Merge "Add carrier config to include LTE bands when calculating NR advaanced threshold"
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 abc196f..b84c8a4 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -286,7 +286,7 @@
for (int i = 0; i < pkgNames.size(); ++i) {
final String pkgName = pkgNames.valueAt(i);
- final boolean isVip = mIrs.isVip(userId, pkgName);
+ final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
@@ -321,7 +321,7 @@
final long nowElapsed = SystemClock.elapsedRealtime();
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
- final boolean isVip = mIrs.isVip(userId, pkgName);
+ final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
@@ -397,7 +397,7 @@
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
final long newBalance = getBalanceLocked(userId, pkgName);
- final boolean isVip = mIrs.isVip(userId, pkgName);
+ final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
@@ -503,7 +503,8 @@
"Tried to adjust system balance for " + appToString(userId, pkgName));
return;
}
- if (mIrs.isVip(userId, pkgName)) {
+ final boolean isVip = mIrs.isVip(userId, pkgName);
+ if (isVip) {
// This could happen if the app was made a VIP after it started performing actions.
// Continue recording the transaction for debugging purposes, but don't let it change
// any numbers.
@@ -536,7 +537,6 @@
mActionAffordabilityNotes.get(userId, pkgName);
if (actionAffordabilityNotes != null) {
final long newBalance = ledger.getCurrentBalance();
- final boolean isVip = mIrs.isVip(userId, pkgName);
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
final boolean isAffordable = isVip
@@ -830,7 +830,6 @@
@GuardedBy("mLock")
void onUserRemovedLocked(final int userId) {
- mScribe.discardLedgersLocked(userId);
mCurrentOngoingEvents.delete(userId);
mBalanceThresholdAlarmQueue.removeAlarmsForUserId(userId);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
index fcb3e67..1ff389d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -16,14 +16,20 @@
package com.android.server.tare;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
+import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.os.RemoteException;
+import com.android.internal.util.ArrayUtils;
+
/** POJO to cache only the information about installed packages that TARE cares about. */
class InstalledPackageInfo {
static final int NO_UID = -1;
@@ -31,14 +37,22 @@
public final int uid;
public final String packageName;
public final boolean hasCode;
+ public final boolean isSystemInstaller;
@Nullable
public final String installerPackageName;
- InstalledPackageInfo(@NonNull PackageInfo packageInfo) {
+ InstalledPackageInfo(@NonNull Context context, @NonNull PackageInfo packageInfo) {
final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
packageName = packageInfo.packageName;
hasCode = applicationInfo != null && applicationInfo.hasCode();
+ isSystemInstaller = applicationInfo != null
+ && ArrayUtils.indexOf(
+ packageInfo.requestedPermissions, Manifest.permission.INSTALL_PACKAGES) >= 0
+ && PackageManager.PERMISSION_GRANTED
+ == PermissionChecker.checkPermissionForPreflight(context,
+ Manifest.permission.INSTALL_PACKAGES, PermissionChecker.PID_UNKNOWN,
+ applicationInfo.uid, packageName);
InstallSourceInfo installSourceInfo = null;
try {
installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName);
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 17b8746..4001d9b 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -64,6 +64,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
import android.util.SparseSetArray;
import com.android.internal.annotations.GuardedBy;
@@ -108,6 +109,16 @@
/** The amount of time to delay reclamation by after boot. */
private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
/**
+ * The amount of time after TARE has first been set up that a system installer will be allowed
+ * expanded credit privileges.
+ */
+ static final long INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS = 7 * DAY_IN_MILLIS;
+ /**
+ * The amount of time to wait after TARE has first been set up before considering adjusting the
+ * stock/consumption limit.
+ */
+ private static final long STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS = 5 * DAY_IN_MILLIS;
+ /**
* The battery level above which we may consider quantitative easing (increasing the consumption
* limit).
*/
@@ -127,7 +138,7 @@
private static final long STOCK_RECALCULATION_MIN_DATA_DURATION_MS = 8 * HOUR_IN_MILLIS;
private static final int PACKAGE_QUERY_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_APEX;
+ | PackageManager.MATCH_APEX | PackageManager.GET_PERMISSIONS;
/** Global lock for all resource economy state. */
private final Object mLock = new Object();
@@ -179,6 +190,13 @@
@GuardedBy("mLock")
private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
+ /**
+ * Set of temporary Very Important Packages and when their VIP status ends, in the elapsed
+ * realtime ({@link android.annotation.ElapsedRealtimeLong}) timebase.
+ */
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, Long> mTemporaryVips = new SparseArrayMap<>();
+
/** Set of apps each installer is responsible for installing. */
@GuardedBy("mLock")
private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>();
@@ -308,6 +326,7 @@
private static final int MSG_PROCESS_USAGE_EVENT = 2;
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENER = 4;
+ private static final int MSG_CLEAN_UP_TEMP_VIP_LIST = 5;
private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
/**
@@ -413,6 +432,13 @@
return userPkgs;
}
+ @Nullable
+ InstalledPackageInfo getInstalledPackageInfo(final int userId, @NonNull final String pkgName) {
+ synchronized (mLock) {
+ return mPkgCache.get(userId, pkgName);
+ }
+ }
+
@GuardedBy("mLock")
long getConsumptionLimitLocked() {
return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100;
@@ -429,6 +455,11 @@
return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
}
+
+ long getRealtimeSinceFirstSetupMs() {
+ return mScribe.getRealtimeSinceFirstSetupMs(SystemClock.elapsedRealtime());
+ }
+
int getUid(final int userId, @NonNull final String pkgName) {
synchronized (mPackageToUidCache) {
Integer uid = mPackageToUidCache.get(userId, pkgName);
@@ -470,6 +501,10 @@
}
boolean isVip(final int userId, @NonNull String pkgName) {
+ return isVip(userId, pkgName, SystemClock.elapsedRealtime());
+ }
+
+ boolean isVip(final int userId, @NonNull String pkgName, final long nowElapsed) {
synchronized (mLock) {
final Boolean override = mVipOverrides.get(userId, pkgName);
if (override != null) {
@@ -481,6 +516,12 @@
// operate.
return true;
}
+ synchronized (mLock) {
+ final Long expirationTimeElapsed = mTemporaryVips.get(userId, pkgName);
+ if (expirationTimeElapsed != null) {
+ return nowElapsed <= expirationTimeElapsed;
+ }
+ }
return false;
}
@@ -569,7 +610,7 @@
mPackageToUidCache.add(userId, pkgName, uid);
}
synchronized (mLock) {
- final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo);
+ final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), packageInfo);
final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
maybeUpdateInstallerStatusLocked(oldIpo, ipo);
mUidToPackageCache.add(uid, pkgName);
@@ -626,11 +667,16 @@
final List<PackageInfo> pkgs =
mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
for (int i = pkgs.size() - 1; i >= 0; --i) {
- final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+ final InstalledPackageInfo ipo =
+ new InstalledPackageInfo(getContext(), pkgs.get(i));
final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
maybeUpdateInstallerStatusLocked(oldIpo, ipo);
}
mAgent.grantBirthrightsLocked(userId);
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ mScribe.setUserAddedTimeLocked(userId, nowElapsed);
+ grantInstallersTemporaryVipStatusLocked(userId,
+ nowElapsed, INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS);
}
}
@@ -647,6 +693,7 @@
mInstallers.delete(userId);
mPkgCache.delete(userId);
mAgent.onUserRemovedLocked(userId);
+ mScribe.onUserRemovedLocked(userId);
}
}
@@ -659,6 +706,10 @@
maybeAdjustDesiredStockLevelLocked();
return;
}
+ if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) {
+ // Things can be very tumultuous soon after first setup.
+ return;
+ }
// We don't need to increase the limit if the device runs out of consumable credits
// when the battery is low.
final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked();
@@ -687,6 +738,10 @@
if (!mConfigObserver.ENABLE_TIP3) {
return;
}
+ if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) {
+ // Things can be very tumultuous soon after first setup.
+ return;
+ }
// Don't adjust the limit too often or while the battery is low.
final long now = getCurrentTimeMillis();
if ((now - mScribe.getLastStockRecalculationTimeLocked()) < STOCK_RECALCULATION_DELAY_MS
@@ -776,6 +831,28 @@
}
@GuardedBy("mLock")
+ private void grantInstallersTemporaryVipStatusLocked(int userId, long nowElapsed,
+ long grantDurationMs) {
+ final long grantEndTimeElapsed = nowElapsed + grantDurationMs;
+ final int uIdx = mPkgCache.indexOfKey(userId);
+ if (uIdx < 0) {
+ return;
+ }
+ for (int pIdx = mPkgCache.numElementsForKey(uIdx) - 1; pIdx >= 0; --pIdx) {
+ final InstalledPackageInfo ipo = mPkgCache.valueAt(uIdx, pIdx);
+
+ if (ipo.isSystemInstaller) {
+ final Long currentGrantEndTimeElapsed = mTemporaryVips.get(userId, ipo.packageName);
+ if (currentGrantEndTimeElapsed == null
+ || currentGrantEndTimeElapsed < grantEndTimeElapsed) {
+ mTemporaryVips.add(userId, ipo.packageName, grantEndTimeElapsed);
+ }
+ }
+ }
+ mHandler.sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST, grantDurationMs);
+ }
+
+ @GuardedBy("mLock")
private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) {
if (!mIsEnabled) {
return;
@@ -870,7 +947,8 @@
final List<PackageInfo> pkgs =
mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
for (int i = pkgs.size() - 1; i >= 0; --i) {
- final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+ final InstalledPackageInfo ipo =
+ new InstalledPackageInfo(getContext(), pkgs.get(i));
final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
maybeUpdateInstallerStatusLocked(oldIpo, ipo);
}
@@ -953,11 +1031,17 @@
synchronized (mLock) {
mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties());
loadInstalledPackageListLocked();
+ final SparseLongArray timeSinceUsersAdded;
final boolean isFirstSetup = !mScribe.recordExists();
+ final long nowElapsed = SystemClock.elapsedRealtime();
if (isFirstSetup) {
mAgent.grantBirthrightsLocked();
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ // Set the last reclamation time to now so we don't start reclaiming assets
+ // too early.
+ mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
+ timeSinceUsersAdded = new SparseLongArray();
} else {
mScribe.loadFromDiskLocked();
if (mScribe.getSatiatedConsumptionLimitLocked()
@@ -971,6 +1055,21 @@
// Adjust the supply in case battery level changed while the device was off.
adjustCreditSupplyLocked(true);
}
+ timeSinceUsersAdded = mScribe.getRealtimeSinceUsersAddedLocked(nowElapsed);
+ }
+
+ final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
+ for (int userId : userIds) {
+ final long timeSinceUserAddedMs = timeSinceUsersAdded.get(userId, 0);
+ // Temporarily mark installers as VIPs so they aren't subject to credit
+ // limits and policies on first boot.
+ if (timeSinceUserAddedMs < INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS) {
+ final long remainingGraceDurationMs =
+ INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS - timeSinceUserAddedMs;
+
+ grantInstallersTemporaryVipStatusLocked(userId, nowElapsed,
+ remainingGraceDurationMs);
+ }
}
scheduleUnusedWealthReclamationLocked();
}
@@ -1079,6 +1178,36 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
+ case MSG_CLEAN_UP_TEMP_VIP_LIST: {
+ removeMessages(MSG_CLEAN_UP_TEMP_VIP_LIST);
+
+ synchronized (mLock) {
+ final long nowElapsed = SystemClock.elapsedRealtime();
+
+ long earliestExpiration = Long.MAX_VALUE;
+ for (int u = 0; u < mTemporaryVips.numMaps(); ++u) {
+ final int userId = mTemporaryVips.keyAt(u);
+
+ for (int p = mTemporaryVips.numElementsForKeyAt(u) - 1; p >= 0; --p) {
+ final String pkgName = mTemporaryVips.keyAt(u, p);
+ final Long expiration = mTemporaryVips.valueAt(u, p);
+
+ if (expiration == null || expiration < nowElapsed) {
+ mTemporaryVips.delete(userId, pkgName);
+ } else {
+ earliestExpiration = Math.min(earliestExpiration, expiration);
+ }
+ }
+ }
+
+ if (earliestExpiration < Long.MAX_VALUE) {
+ sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST,
+ earliestExpiration - nowElapsed);
+ }
+ }
+ }
+ break;
+
case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
final SomeArgs args = (SomeArgs) msg.obj;
final int userId = args.argi1;
@@ -1558,6 +1687,7 @@
boolean printedVips = false;
pw.println();
pw.print("VIPs:");
+ pw.increaseIndent();
for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
final int userId = mVipOverrides.keyAt(u);
@@ -1576,6 +1706,32 @@
} else {
pw.print(" None");
}
+ pw.decreaseIndent();
+ pw.println();
+
+ boolean printedTempVips = false;
+ pw.println();
+ pw.print("Temp VIPs:");
+ pw.increaseIndent();
+ for (int u = 0; u < mTemporaryVips.numMaps(); ++u) {
+ final int userId = mTemporaryVips.keyAt(u);
+
+ for (int p = 0; p < mTemporaryVips.numElementsForKeyAt(u); ++p) {
+ final String pkgName = mTemporaryVips.keyAt(u, p);
+
+ printedTempVips = true;
+ pw.println();
+ pw.print(appToString(userId, pkgName));
+ pw.print("=");
+ pw.print(mTemporaryVips.valueAt(u, p));
+ }
+ }
+ if (printedTempVips) {
+ pw.println();
+ } else {
+ pw.print(" None");
+ }
+ pw.decreaseIndent();
pw.println();
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 7cf459c..c2a6e43 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -117,6 +117,7 @@
import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE;
import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE;
+import static com.android.server.tare.TareUtils.appToString;
import static com.android.server.tare.TareUtils.cakeToString;
import android.annotation.NonNull;
@@ -210,6 +211,22 @@
if (mIrs.isPackageRestricted(userId, pkgName)) {
return 0;
}
+ final InstalledPackageInfo ipo = mIrs.getInstalledPackageInfo(userId, pkgName);
+ if (ipo == null) {
+ Slog.wtfStack(TAG,
+ "Tried to get max balance of invalid app: " + appToString(userId, pkgName));
+ } else {
+ // A system installer's max balance is elevated for some time after first boot so
+ // they can use jobs to download and install apps.
+ if (ipo.isSystemInstaller) {
+ final long timeSinceFirstSetupMs = mIrs.getRealtimeSinceFirstSetupMs();
+ final boolean stillExempted = timeSinceFirstSetupMs
+ < InternalResourceService.INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS;
+ if (stillExempted) {
+ return mMaxSatiatedConsumptionLimit;
+ }
+ }
+ }
return mMaxSatiatedBalance;
}
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 ee448b5..b41c0d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Environment;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -33,6 +34,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -88,6 +90,7 @@
"lastStockRecalculationTime";
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_TIME_SINCE_FIRST_SETUP_MS = "timeSinceFirstSetup";
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";
@@ -112,6 +115,11 @@
private final InternalResourceService mIrs;
private final Analyst mAnalyst;
+ /**
+ * The value of elapsed realtime since TARE was first setup that was read from disk.
+ * This will only be changed when the persisted file is read.
+ */
+ private long mLoadedTimeSinceFirstSetup;
@GuardedBy("mIrs.getLock()")
private long mLastReclamationTime;
@GuardedBy("mIrs.getLock()")
@@ -122,6 +130,9 @@
private long mRemainingConsumableCakes;
@GuardedBy("mIrs.getLock()")
private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
+ /** Offsets used to calculate the total realtime since each user was added. */
+ @GuardedBy("mIrs.getLock()")
+ private final SparseLongArray mRealtimeSinceUsersAddedOffsets = new SparseLongArray();
private final Runnable mCleanRunnable = this::cleanupLedgers;
private final Runnable mWriteRunnable = this::writeState;
@@ -163,8 +174,9 @@
}
@GuardedBy("mIrs.getLock()")
- void discardLedgersLocked(final int userId) {
+ void onUserRemovedLocked(final int userId) {
mLedgers.delete(userId);
+ mRealtimeSinceUsersAddedOffsets.delete(userId);
postWrite();
}
@@ -215,6 +227,11 @@
return sum;
}
+ /** Returns the cumulative elapsed realtime since TARE was first setup. */
+ long getRealtimeSinceFirstSetupMs(long nowElapsed) {
+ return mLoadedTimeSinceFirstSetup + nowElapsed;
+ }
+
/** Returns the total amount of cakes that remain to be consumed. */
@GuardedBy("mIrs.getLock()")
long getRemainingConsumableCakesLocked() {
@@ -222,6 +239,16 @@
}
@GuardedBy("mIrs.getLock()")
+ SparseLongArray getRealtimeSinceUsersAddedLocked(long nowElapsed) {
+ final SparseLongArray realtimes = new SparseLongArray();
+ for (int i = mRealtimeSinceUsersAddedOffsets.size() - 1; i >= 0; --i) {
+ realtimes.put(mRealtimeSinceUsersAddedOffsets.keyAt(i),
+ mRealtimeSinceUsersAddedOffsets.valueAt(i) + nowElapsed);
+ }
+ return realtimes;
+ }
+
+ @GuardedBy("mIrs.getLock()")
void loadFromDiskLocked() {
mLedgers.clear();
if (!recordExists()) {
@@ -276,7 +303,8 @@
}
}
- final long endTimeCutoff = System.currentTimeMillis() - MAX_TRANSACTION_AGE_MS;
+ final long now = System.currentTimeMillis();
+ final long endTimeCutoff = now - MAX_TRANSACTION_AGE_MS;
long earliestEndTime = Long.MAX_VALUE;
for (eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
eventType = parser.next()) {
@@ -294,6 +322,12 @@
parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
mLastStockRecalculationTime = parser.getAttributeLong(null,
XML_ATTR_LAST_STOCK_RECALCULATION_TIME, 0);
+ mLoadedTimeSinceFirstSetup =
+ parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+ // If there's no recorded time since first setup, then
+ // offset the current elapsed time so it doesn't shift the
+ // timing too much.
+ -SystemClock.elapsedRealtime());
mSatiatedConsumptionLimit =
parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
mIrs.getInitialSatiatedConsumptionLimitLocked());
@@ -356,6 +390,13 @@
}
@GuardedBy("mIrs.getLock()")
+ void setUserAddedTimeLocked(int userId, long timeElapsed) {
+ // Use the current time as an offset so that when we persist the time, it correctly persists
+ // as "time since now".
+ mRealtimeSinceUsersAddedOffsets.put(userId, -timeElapsed);
+ }
+
+ @GuardedBy("mIrs.getLock()")
void tearDownLocked() {
TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
@@ -486,6 +527,14 @@
// Don't return early since we need to go through all the ledger tags and get to the end
// of the user tag.
}
+ if (curUser != UserHandle.USER_NULL) {
+ mRealtimeSinceUsersAddedOffsets.put(curUser,
+ parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+ // If there's no recorded time since first setup, then
+ // offset the current elapsed time so it doesn't shift the
+ // timing too much.
+ -SystemClock.elapsedRealtime()));
+ }
long earliestEndTime = Long.MAX_VALUE;
for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
@@ -630,6 +679,8 @@
out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
out.attributeLong(null,
XML_ATTR_LAST_STOCK_RECALCULATION_TIME, mLastStockRecalculationTime);
+ out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+ mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime());
out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_CAKES,
mRemainingConsumableCakes);
@@ -665,6 +716,9 @@
out.startTag(null, XML_TAG_USER);
out.attributeInt(null, XML_ATTR_USER_ID, userId);
+ out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+ mRealtimeSinceUsersAddedOffsets.get(userId,
+ mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime()));
for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
final String pkgName = mLedgers.keyAt(uIdx, pIdx);
final Ledger ledger = mLedgers.get(userId, pkgName);
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
index 20aa7d3..a8aa033 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -38,9 +38,10 @@
constexpr const mode_t kIdmapFilePermission = S_IRUSR | S_IWUSR; // u=rw-, g=---, o=---
extern "C" bool
-CreateFrroFile(std::string& out_err_result, std::string& packageName, std::string& overlayName,
- std::string& targetPackageName, std::optional<std::string>& targetOverlayable,
- std::vector<FabricatedOverlayEntryParameters>& entries_params,
+CreateFrroFile(std::string& out_err_result, const std::string& packageName,
+ const std::string& overlayName, const std::string& targetPackageName,
+ const std::optional<std::string>& targetOverlayable,
+ const std::vector<FabricatedOverlayEntryParameters>& entries_params,
const std::string& frro_file_path) {
android::idmap2::FabricatedOverlay::Builder builder(packageName, overlayName,
targetPackageName);
@@ -90,9 +91,46 @@
return true;
}
+static PolicyBitmask GetFulfilledPolicy(const bool isSystem, const bool isVendor,
+ const bool isProduct, const bool isTargetSignature,
+ const bool isOdm, const bool isOem) {
+ auto fulfilled_policy = static_cast<PolicyBitmask>(PolicyFlags::PUBLIC);
+
+ if (isSystem) {
+ fulfilled_policy |= PolicyFlags::SYSTEM_PARTITION;
+ }
+ if (isVendor) {
+ fulfilled_policy |= PolicyFlags::VENDOR_PARTITION;
+ }
+ if (isProduct) {
+ fulfilled_policy |= PolicyFlags::PRODUCT_PARTITION;
+ }
+ if (isOdm) {
+ fulfilled_policy |= PolicyFlags::ODM_PARTITION;
+ }
+ if (isOem) {
+ fulfilled_policy |= PolicyFlags::OEM_PARTITION;
+ }
+ if (isTargetSignature) {
+ fulfilled_policy |= PolicyFlags::SIGNATURE;
+ }
+
+ // Not support actor_signature and config_overlay_signature
+ fulfilled_policy &=
+ ~(PolicyFlags::ACTOR_SIGNATURE | PolicyFlags::CONFIG_SIGNATURE);
+
+ ALOGV(
+ "fulfilled_policy = 0x%08x, isSystem = %d, isVendor = %d, isProduct = %d,"
+ " isTargetSignature = %d, isOdm = %d, isOem = %d,",
+ fulfilled_policy, isSystem, isVendor, isProduct, isTargetSignature, isOdm, isOem);
+ return fulfilled_policy;
+}
+
extern "C" bool
CreateIdmapFile(std::string& out_err, const std::string& targetPath, const std::string& overlayPath,
- const std::string& idmapPath, const std::string& overlayName) {
+ const std::string& idmapPath, const std::string& overlayName,
+ const bool isSystem, const bool isVendor, const bool isProduct,
+ const bool isTargetSignature, const bool isOdm, const bool isOem) {
// idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap
// guarantees that existing memory maps will continue to be valid and unaffected. The file must
// be deleted before attempting to create the idmap, so that if idmap creation fails, the
@@ -114,14 +152,11 @@
}
// Overlay self target process. Only allow self-targeting types.
- const auto fulfilled_policies = static_cast<PolicyBitmask>(
- PolicyFlags::PUBLIC | PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION |
- PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE | PolicyFlags::ODM_PARTITION |
- PolicyFlags::OEM_PARTITION | PolicyFlags::ACTOR_SIGNATURE |
- PolicyFlags::CONFIG_SIGNATURE);
+ const auto fulfilled_policies = GetFulfilledPolicy(isSystem, isVendor, isProduct,
+ isTargetSignature, isOdm, isOem);
const auto idmap = Idmap::FromContainers(**target, **overlay, overlayName,
- fulfilled_policies, false /* enforce_overlayable */);
+ fulfilled_policies, true /* enforce_overlayable */);
if (!idmap) {
out_err = base::StringPrintf("Failed to create idmap because of %s",
idmap.GetErrorMessage().c_str());
diff --git a/core/api/current.txt b/core/api/current.txt
index 5b5a289..aa97849 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11735,6 +11735,7 @@
method @NonNull public int[] getChildSessionIds();
method @NonNull public String[] getNames() throws java.io.IOException;
method public int getParentSessionId();
+ method public boolean isKeepApplicationEnabledSetting();
method public boolean isMultiPackage();
method public boolean isStaged();
method @NonNull public java.io.InputStream openRead(@NonNull String) throws java.io.IOException;
@@ -11786,6 +11787,7 @@
method public boolean hasParentSessionId();
method public boolean isActive();
method public boolean isCommitted();
+ method public boolean isKeepApplicationEnabledSetting();
method public boolean isMultiPackage();
method public boolean isSealed();
method public boolean isStaged();
@@ -11818,6 +11820,7 @@
method public void setInstallLocation(int);
method public void setInstallReason(int);
method public void setInstallScenario(int);
+ method public void setKeepApplicationEnabledSetting();
method public void setMultiPackage();
method public void setOriginatingUid(int);
method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -13018,8 +13021,8 @@
public final class CredentialManager {
method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.CredentialManagerException>);
- method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
- method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
+ method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
+ method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
}
public class CredentialManagerException extends java.lang.Exception {
@@ -35877,6 +35880,7 @@
method public static boolean canDrawOverlays(android.content.Context);
field public static final String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
field public static final String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS";
+ field public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS = "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS";
field public static final String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS";
field public static final String ACTION_ALL_APPS_NOTIFICATION_SETTINGS = "android.settings.ALL_APPS_NOTIFICATION_SETTINGS";
field public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS";
@@ -35925,7 +35929,6 @@
field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
- field public static final String ACTION_MEMTAG_SETTINGS = "android.settings.MEMTAG_SETTINGS";
field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS";
field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS";
field public static final String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS";
@@ -39328,6 +39331,149 @@
}
+package android.service.credentials {
+
+ public final class Action implements android.os.Parcelable {
+ ctor public Action(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+ method public int describeContents();
+ method @NonNull public android.app.PendingIntent getPendingIntent();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.Action> CREATOR;
+ }
+
+ public final class BeginCreateCredentialRequest implements android.os.Parcelable {
+ ctor public BeginCreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public String getCallingPackage();
+ method @NonNull public android.os.Bundle getData();
+ method @NonNull public String getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialRequest> CREATOR;
+ }
+
+ public final class BeginCreateCredentialResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.service.credentials.CreateEntry> getCreateEntries();
+ method @Nullable public android.service.credentials.CreateEntry getRemoteCreateEntry();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialResponse> CREATOR;
+ }
+
+ public static final class BeginCreateCredentialResponse.Builder {
+ ctor public BeginCreateCredentialResponse.Builder();
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.CreateEntry);
+ }
+
+ public final class CreateCredentialRequest implements android.os.Parcelable {
+ ctor public CreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public String getCallingPackage();
+ method @NonNull public android.os.Bundle getData();
+ method @NonNull public String getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateCredentialRequest> CREATOR;
+ }
+
+ public final class CreateEntry implements android.os.Parcelable {
+ ctor public CreateEntry(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+ method public int describeContents();
+ method @NonNull public android.app.PendingIntent getPendingIntent();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateEntry> CREATOR;
+ }
+
+ public final class CredentialEntry implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.credentials.Credential getCredential();
+ method @Nullable public android.app.PendingIntent getPendingIntent();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method @NonNull public String getType();
+ method public boolean isAutoSelectAllowed();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialEntry> CREATOR;
+ }
+
+ public static final class CredentialEntry.Builder {
+ ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+ ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.credentials.Credential);
+ method @NonNull public android.service.credentials.CredentialEntry build();
+ method @NonNull public android.service.credentials.CredentialEntry.Builder setAutoSelectAllowed(@NonNull boolean);
+ }
+
+ public class CredentialProviderException extends java.lang.Exception {
+ ctor public CredentialProviderException(int, @NonNull String, @NonNull Throwable);
+ ctor public CredentialProviderException(int, @NonNull String);
+ ctor public CredentialProviderException(int, @NonNull Throwable);
+ ctor public CredentialProviderException(int);
+ method public int getErrorCode();
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract class CredentialProviderService extends android.app.Service {
+ ctor public CredentialProviderService();
+ method public abstract void onBeginCreateCredential(@NonNull android.service.credentials.BeginCreateCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginCreateCredentialResponse,android.service.credentials.CredentialProviderException>);
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onGetCredentials(@NonNull android.service.credentials.GetCredentialsRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.GetCredentialsResponse,android.service.credentials.CredentialProviderException>);
+ field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+ field public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
+ field public static final String EXTRA_CREATE_CREDENTIAL_RESULT = "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
+ field public static final String EXTRA_CREDENTIAL_RESULT = "android.service.credentials.extra.CREDENTIAL_RESULT";
+ field public static final String EXTRA_ERROR = "android.service.credentials.extra.ERROR";
+ field public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT = "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+ field public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService";
+ }
+
+ public final class CredentialsResponseContent implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.service.credentials.Action> getActions();
+ method @NonNull public java.util.List<android.service.credentials.CredentialEntry> getCredentialEntries();
+ method @Nullable public android.service.credentials.CredentialEntry getRemoteCredentialEntry();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialsResponseContent> CREATOR;
+ }
+
+ public static final class CredentialsResponseContent.Builder {
+ ctor public CredentialsResponseContent.Builder();
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addAction(@NonNull android.service.credentials.Action);
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addCredentialEntry(@NonNull android.service.credentials.CredentialEntry);
+ method @NonNull public android.service.credentials.CredentialsResponseContent build();
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.CredentialEntry);
+ }
+
+ public final class GetCredentialsRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getCallingPackage();
+ method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsRequest> CREATOR;
+ }
+
+ public static final class GetCredentialsRequest.Builder {
+ ctor public GetCredentialsRequest.Builder(@NonNull String);
+ method @NonNull public android.service.credentials.GetCredentialsRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+ method @NonNull public android.service.credentials.GetCredentialsRequest build();
+ method @NonNull public android.service.credentials.GetCredentialsRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
+ }
+
+ public final class GetCredentialsResponse implements android.os.Parcelable {
+ method @NonNull public static android.service.credentials.GetCredentialsResponse createWithAuthentication(@NonNull android.service.credentials.Action);
+ method @NonNull public static android.service.credentials.GetCredentialsResponse createWithResponseContent(@NonNull android.service.credentials.CredentialsResponseContent);
+ method public int describeContents();
+ method @Nullable public android.service.credentials.Action getAuthenticationAction();
+ method @Nullable public android.service.credentials.CredentialsResponseContent getCredentialsResponseContent();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsResponse> CREATOR;
+ }
+
+}
+
package android.service.dreams {
public class DreamService extends android.app.Service implements android.view.Window.Callback {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eac990d..057c1ada 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5534,6 +5534,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_ACCESSORY_HANDSHAKE = "android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE";
field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED = "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED";
field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
field public static final String EXTRA_ACCESSORY_HANDSHAKE_END = "android.hardware.usb.extra.ACCESSORY_HANDSHAKE_END";
field public static final String EXTRA_ACCESSORY_START = "android.hardware.usb.extra.ACCESSORY_START";
@@ -5561,6 +5562,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus();
method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbPort(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int);
+ method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public boolean supportsComplianceWarnings();
field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1
field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2
field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; // 0x4
@@ -5586,6 +5588,7 @@
public final class UsbPortStatus implements android.os.Parcelable {
method public int describeContents();
+ method @CheckResult @NonNull public int[] getComplianceWarnings();
method public int getCurrentDataRole();
method public int getCurrentMode();
method public int getCurrentPowerRole();
@@ -5596,6 +5599,10 @@
method public boolean isPowerTransferLimited();
method public boolean isRoleCombinationSupported(int, int);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int COMPLIANCE_WARNING_BC_1_2 = 3; // 0x3
+ field public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2; // 0x2
+ field public static final int COMPLIANCE_WARNING_MISSING_RP = 4; // 0x4
+ field public static final int COMPLIANCE_WARNING_OTHER = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR;
field public static final int DATA_ROLE_DEVICE = 2; // 0x2
field public static final int DATA_ROLE_HOST = 1; // 0x1
@@ -6474,7 +6481,6 @@
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
- field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20
field public static final int PLAYER_STATE_IDLE = 1; // 0x1
field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
@@ -7269,6 +7275,7 @@
method @NonNull public java.util.List<android.media.tv.tuner.frontend.FrontendStatusReadiness> getFrontendStatusReadiness(@NonNull int[]);
method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
+ method public boolean isLnaSupported();
method public boolean isLowestPriority(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
@@ -12818,14 +12825,14 @@
method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
}
- @Deprecated public final class CallAttributes implements android.os.Parcelable {
- ctor @Deprecated public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
- method @Deprecated public int describeContents();
- method @Deprecated @NonNull public android.telephony.CallQuality getCallQuality();
- method @Deprecated public int getNetworkType();
- method @Deprecated @NonNull public android.telephony.PreciseCallState getPreciseCallState();
- method @Deprecated public void writeToParcel(android.os.Parcel, int);
- field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
+ public final class CallAttributes implements android.os.Parcelable {
+ ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
+ method public int describeContents();
+ method @NonNull public android.telephony.CallQuality getCallQuality();
+ method public int getNetworkType();
+ method @NonNull public android.telephony.PreciseCallState getPreciseCallState();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
}
public final class CallForwardingInfo implements android.os.Parcelable {
@@ -12905,28 +12912,6 @@
method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int);
}
- public final class CallState implements android.os.Parcelable {
- method public int describeContents();
- method @Nullable public android.telephony.CallQuality getCallQuality();
- method public int getCallState();
- method public int getImsCallServiceType();
- method @Nullable public String getImsCallSessionId();
- method public int getImsCallType();
- method public int getNetworkType();
- method public void writeToParcel(@Nullable android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallState> CREATOR;
- }
-
- public static final class CallState.Builder {
- ctor public CallState.Builder(int);
- method @NonNull public android.telephony.CallState build();
- method @NonNull public android.telephony.CallState.Builder setCallQuality(@Nullable android.telephony.CallQuality);
- method @NonNull public android.telephony.CallState.Builder setImsCallServiceType(int);
- method @NonNull public android.telephony.CallState.Builder setImsCallSessionId(@Nullable String);
- method @NonNull public android.telephony.CallState.Builder setImsCallType(int);
- method @NonNull public android.telephony.CallState.Builder setNetworkType(int);
- }
-
public class CarrierConfigManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -13677,8 +13662,7 @@
}
public static interface TelephonyCallback.CallAttributesListener {
- method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
+ method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
}
public static interface TelephonyCallback.DataEnabledListener {
@@ -14779,7 +14763,6 @@
field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3
field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0
field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1
- field public static final int CALL_TYPE_NONE = 0; // 0x0
field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3
field public static final int CALL_TYPE_VOICE = 2; // 0x2
field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1
@@ -16144,6 +16127,12 @@
public abstract class AccessibilityDisplayProxy {
ctor public AccessibilityDisplayProxy(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
method public int getDisplayId();
+ method @NonNull public final java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAndEnabledServices();
+ method @NonNull public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
+ method public void interrupt();
+ method public void onAccessibilityEvent(@NonNull android.view.accessibility.AccessibilityEvent);
+ method public void onProxyConnected();
+ method public void setInstalledAndEnabledServices(@NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
}
public final class AccessibilityManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 121741e0..35e01f1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -459,6 +459,8 @@
public class WallpaperManager {
method @Nullable public android.graphics.Bitmap getBitmap();
+ method @Nullable public android.graphics.Rect peekBitmapDimensions();
+ method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
method public boolean shouldEnableWideColorGamut();
method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5aa8f1f..884870b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6426,23 +6426,28 @@
}
private void handleTrimMemory(int level) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory: " + level);
+ }
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
- if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- PropertyInvalidatedCache.onTrimMemory();
- }
+ try {
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+ PropertyInvalidatedCache.onTrimMemory();
+ }
- final ArrayList<ComponentCallbacks2> callbacks =
- collectComponentCallbacks(true /* includeUiContexts */);
+ final ArrayList<ComponentCallbacks2> callbacks =
+ collectComponentCallbacks(true /* includeUiContexts */);
- final int N = callbacks.size();
- for (int i = 0; i < N; i++) {
- callbacks.get(i).onTrimMemory(level);
+ final int N = callbacks.size();
+ for (int i = 0; i < N; i++) {
+ callbacks.get(i).onTrimMemory(level);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
WindowManagerGlobal.getInstance().trimMemory(level);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
unscheduleGcIdler();
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 818bdc2..9bf8550 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -54,6 +54,7 @@
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
+import android.healthconnect.HealthConnectManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArraySet;
@@ -66,8 +67,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Optional;
+import java.util.Set;
/**
* This class enforces the policies around the foreground service types.
@@ -655,11 +658,12 @@
*
* For test only.
*/
- public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest() {
+ public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest(
+ @NonNull Context context) {
if (mAllOfPermissions == null) {
return Optional.empty();
}
- return Optional.of(mAllOfPermissions.toStringArray());
+ return Optional.of(mAllOfPermissions.toStringArray(context));
}
/**
@@ -668,11 +672,12 @@
*
* For test only.
*/
- public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest() {
+ public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest(
+ @NonNull Context context) {
if (mAnyOfPermissions == null) {
return Optional.empty();
}
- return Optional.of(mAnyOfPermissions.toStringArray());
+ return Optional.of(mAnyOfPermissions.toStringArray(context));
}
/**
@@ -808,12 +813,12 @@
return sb.toString();
}
- @NonNull String[] toStringArray() {
- final String[] names = new String[mPermissions.length];
+ @NonNull String[] toStringArray(Context context) {
+ final ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < mPermissions.length; i++) {
- names[i] = mPermissions[i].mName;
+ mPermissions[i].addToList(context, list);
}
- return names;
+ return list.toArray(new String[list.size()]);
}
}
@@ -826,7 +831,7 @@
/**
* The name of this permission.
*/
- final @NonNull String mName;
+ protected final @NonNull String mName;
/**
* Constructor.
@@ -846,6 +851,10 @@
public String toString() {
return mName;
}
+
+ void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+ list.add(mName);
+ }
}
/**
@@ -859,15 +868,23 @@
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
+ return checkPermission(context, mName, callerUid, callerPid, packageName,
+ allowWhileInUse);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
+ int callerPid, String packageName, boolean allowWhileInUse) {
// Simple case, check if it's already granted.
- if (context.checkPermission(mName, callerPid, callerUid) == PERMISSION_GRANTED) {
+ if (context.checkPermission(name, callerPid, callerUid) == PERMISSION_GRANTED) {
return PERMISSION_GRANTED;
}
if (allowWhileInUse) {
// Check its appops
- final int opCode = AppOpsManager.permissionToOpCode(mName);
+ final int opCode = AppOpsManager.permissionToOpCode(name);
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
if (opCode != AppOpsManager.OP_NONE) {
final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
@@ -895,7 +912,7 @@
@Override
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
final int mode = appOpsManager.unsafeCheckOpRawNoThrow(mOpCode, callerUid, packageName);
@@ -915,7 +932,7 @@
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
final UsbManager usbManager = context.getSystemService(UsbManager.class);
final HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
@@ -941,7 +958,7 @@
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
final UsbManager usbManager = context.getSystemService(UsbManager.class);
final UsbAccessory[] accessories = usbManager.getAccessoryList();
@@ -956,6 +973,45 @@
}
}
+ static class HealthConnectPermission extends RegularPermission {
+ private @Nullable String[] mPermissionNames;
+
+ HealthConnectPermission() {
+ super("Health Connect");
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final String[] perms = getPermissions(context);
+ for (String perm : perms) {
+ if (checkPermission(context, perm, callerUid, callerPid,
+ packageName, allowWhileInUse) == PERMISSION_GRANTED) {
+ return PERMISSION_GRANTED;
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+
+ @Override
+ void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+ final String[] perms = getPermissions(context);
+ for (String perm : perms) {
+ list.add(perm);
+ }
+ }
+
+ private @NonNull String[] getPermissions(@NonNull Context context) {
+ if (mPermissionNames != null) {
+ return mPermissionNames;
+ }
+ final Set<String> healthPerms = HealthConnectManager.getHealthPermissions(context);
+ return mPermissionNames = healthPerms.toArray(new String[healthPerms.size()]);
+ }
+ }
+
/**
* The default policy for the foreground service types.
*
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e54a084..5d87012 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -58,6 +58,7 @@
import android.companion.ICompanionDeviceManager;
import android.companion.virtual.IVirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager;
+import android.compat.Compatibility;
import android.content.ClipboardManager;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -1092,7 +1093,10 @@
new CachedServiceFetcher<OverlayManager>() {
@Override
public OverlayManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
+ final IBinder b =
+ (Compatibility.isChangeEnabled(OverlayManager.SELF_TARGETING_OVERLAY))
+ ? ServiceManager.getService(Context.OVERLAY_SERVICE)
+ : ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
return new OverlayManager(ctx, IOverlayManager.Stub.asInterface(b));
}});
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index ef10c0b..f133c8a 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -201,6 +201,23 @@
"file_patterns": [
"(/|^)PropertyInvalidatedCache.java"
]
+ },
+ {
+ "name": "FrameworksCoreGameManagerTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.app"
+ }
+ ],
+ "file_patterns": [
+ "(/|^)GameManager[^/]*", "(/|^)GameMode[^/]*"
+ ]
}
],
"presubmit-large": [
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8685259..f5d657c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -358,13 +358,37 @@
}
}
+ /**
+ * Convenience class representing a cached wallpaper bitmap and associated data.
+ */
+ private static class CachedWallpaper {
+ final Bitmap mCachedWallpaper;
+ final int mCachedWallpaperUserId;
+ @SetWallpaperFlags final int mWhich;
+
+ CachedWallpaper(Bitmap cachedWallpaper, int cachedWallpaperUserId,
+ @SetWallpaperFlags int which) {
+ mCachedWallpaper = cachedWallpaper;
+ mCachedWallpaperUserId = cachedWallpaperUserId;
+ mWhich = which;
+ }
+
+ /**
+ * Returns true if this object represents a valid cached bitmap for the given parameters,
+ * otherwise false.
+ */
+ boolean isValid(int userId, @SetWallpaperFlags int which) {
+ return userId == mCachedWallpaperUserId && which == mWhich
+ && !mCachedWallpaper.isRecycled();
+ }
+ }
+
private static class Globals extends IWallpaperManagerCallback.Stub {
private final IWallpaperManager mService;
private boolean mColorCallbackRegistered;
private final ArrayList<Pair<OnColorsChangedListener, Handler>> mColorListeners =
new ArrayList<>();
- private Bitmap mCachedWallpaper;
- private int mCachedWallpaperUserId;
+ private CachedWallpaper mCachedWallpaper;
private Bitmap mDefaultWallpaper;
private Handler mMainLooperHandler;
private ArrayMap<LocalWallpaperColorConsumer, ArraySet<RectF>> mLocalColorCallbackAreas =
@@ -536,6 +560,15 @@
false /* hardware */, cmProxy);
}
+ /**
+ * Retrieves the current wallpaper Bitmap, caching the result. If this fails and
+ * `returnDefault` is set, returns the Bitmap for the default wallpaper; otherwise returns
+ * null.
+ *
+ * More sophisticated caching might a) store and compare the wallpaper ID so that
+ * consecutive calls for FLAG_SYSTEM and FLAG_LOCK could return the cached wallpaper if
+ * no lock screen wallpaper is set, or b) separately cache home and lock screen wallpaper.
+ */
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
@SetWallpaperFlags int which, int userId, boolean hardware,
ColorManagementProxy cmProxy) {
@@ -549,16 +582,14 @@
}
}
synchronized (this) {
- if (mCachedWallpaper != null && mCachedWallpaperUserId == userId
- && !mCachedWallpaper.isRecycled()) {
- return mCachedWallpaper;
+ if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) {
+ return mCachedWallpaper.mCachedWallpaper;
}
mCachedWallpaper = null;
- mCachedWallpaperUserId = 0;
+ Bitmap currentWallpaper = null;
try {
- mCachedWallpaper = getCurrentWallpaperLocked(
- context, userId, hardware, cmProxy);
- mCachedWallpaperUserId = userId;
+ currentWallpaper = getCurrentWallpaperLocked(
+ context, which, userId, hardware, cmProxy);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
} catch (SecurityException e) {
@@ -570,8 +601,9 @@
throw e;
}
}
- if (mCachedWallpaper != null) {
- return mCachedWallpaper;
+ if (currentWallpaper != null) {
+ mCachedWallpaper = new CachedWallpaper(currentWallpaper, userId, which);
+ return currentWallpaper;
}
}
if (returnDefault) {
@@ -587,7 +619,9 @@
return null;
}
- public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) {
+ @Nullable
+ public Rect peekWallpaperDimensions(Context context, boolean returnDefault,
+ @SetWallpaperFlags int which, int userId) {
if (mService != null) {
try {
if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -600,11 +634,10 @@
Rect dimensions = null;
synchronized (this) {
- ParcelFileDescriptor pfd = null;
- try {
- Bundle params = new Bundle();
- pfd = mService.getWallpaperWithFeature(context.getOpPackageName(),
- context.getAttributionTag(), this, FLAG_SYSTEM, params, userId);
+ Bundle params = new Bundle();
+ try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
+ context.getOpPackageName(), context.getAttributionTag(), this, which,
+ params, userId)) {
// Let's peek user wallpaper first.
if (pfd != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -614,19 +647,14 @@
}
} catch (RemoteException ex) {
Log.w(TAG, "peek wallpaper dimensions failed", ex);
- } finally {
- if (pfd != null) {
- try {
- pfd.close();
- } catch (IOException ignored) {
- }
- }
+ } catch (IOException ignored) {
+ // This is only thrown on close and can be safely ignored.
}
}
// If user wallpaper is unavailable, may be the default one instead.
if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
&& returnDefault) {
- InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM);
+ InputStream is = openDefaultWallpaper(context, which);
if (is != null) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -644,13 +672,12 @@
void forgetLoadedWallpaper() {
synchronized (this) {
mCachedWallpaper = null;
- mCachedWallpaperUserId = 0;
mDefaultWallpaper = null;
}
}
- private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware,
- ColorManagementProxy cmProxy) {
+ private Bitmap getCurrentWallpaperLocked(Context context, @SetWallpaperFlags int which,
+ int userId, boolean hardware, ColorManagementProxy cmProxy) {
if (mService == null) {
Log.w(TAG, "WallpaperService not running");
return null;
@@ -659,7 +686,7 @@
try {
Bundle params = new Bundle();
ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
- context.getOpPackageName(), context.getAttributionTag(), this, FLAG_SYSTEM,
+ context.getOpPackageName(), context.getAttributionTag(), this, which,
params, userId);
if (pfd != null) {
@@ -1148,10 +1175,10 @@
* @return the dimensions of system wallpaper
* @hide
*/
+ @TestApi
@Nullable
public Rect peekBitmapDimensions() {
- return sGlobals.peekWallpaperDimensions(
- mContext, true /* returnDefault */, mContext.getUserId());
+ return peekBitmapDimensions(FLAG_SYSTEM);
}
/**
@@ -1162,9 +1189,12 @@
* @return the dimensions of system wallpaper
* @hide
*/
+ @TestApi
@Nullable
public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
- return peekBitmapDimensions();
+ checkExactlyOneWallpaperFlagSet(which);
+ return sGlobals.peekWallpaperDimensions(mContext, true /* returnDefault */, which,
+ mContext.getUserId());
}
/**
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
index ec10d84..6b5e667 100644
--- a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
@@ -19,6 +19,7 @@
import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString;
import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
@@ -319,6 +320,40 @@
mSecondaryProviderStatus, mSecondaryProviderReportedStatus);
}
+ /**
+ * Returns {@code true} if the algorithm status could allow the time zone detector to enter
+ * telephony fallback mode.
+ */
+ public boolean couldEnableTelephonyFallback() {
+ if (mStatus == DETECTION_ALGORITHM_STATUS_UNKNOWN
+ || mStatus == DETECTION_ALGORITHM_STATUS_NOT_RUNNING
+ || mStatus == DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED) {
+ // This method is not expected to be called on objects with these statuses. Fallback
+ // should not be enabled if it is.
+ return false;
+ }
+
+ // mStatus == DETECTOR_STATUS_RUNNING.
+
+ boolean primarySuggestsFallback = false;
+ if (mPrimaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) {
+ primarySuggestsFallback = true;
+ } else if (mPrimaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN
+ && mPrimaryProviderReportedStatus != null) {
+ primarySuggestsFallback = mPrimaryProviderReportedStatus.couldEnableTelephonyFallback();
+ }
+
+ boolean secondarySuggestsFallback = false;
+ if (mSecondaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) {
+ secondarySuggestsFallback = true;
+ } else if (mSecondaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN
+ && mSecondaryProviderReportedStatus != null) {
+ secondarySuggestsFallback =
+ mSecondaryProviderReportedStatus.couldEnableTelephonyFallback();
+ }
+ return primarySuggestsFallback && secondarySuggestsFallback;
+ }
+
/** @hide */
@VisibleForTesting
@NonNull
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 94275ae..812f6b0 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -92,6 +92,21 @@
private static final long THROW_SECURITY_EXCEPTIONS = 147340954;
/**
+ * Applications can use OverlayManager to create overlays to overlay on itself resources. The
+ * overlay target is itself and the work range is only in caller application.
+ *
+ * <p>In {@link android.content.Context#getSystemService(String)}, it crashes because of {@link
+ * java.lang.NullPointerException} if the parameter is OverlayManager. if the self-targeting is
+ * enabled, the caller application can get the OverlayManager instance to use self-targeting
+ * functionality.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long SELF_TARGETING_OVERLAY = 205919743;
+
+ /**
* Creates a new instance.
*
* @param context The current context in which to operate.
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 1fc6bda..7d9c64a 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -61,4 +61,6 @@
int getInstallFlags();
void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
+
+ boolean isKeepApplicationEnabledSetting();
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d7686e2..3551827 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1717,6 +1717,18 @@
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * @return {@code true} if this session will keep the existing application enabled setting
+ * after installation.
+ */
+ public boolean isKeepApplicationEnabledSetting() {
+ try {
+ return mSession.isKeepApplicationEnabledSetting();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -1855,6 +1867,8 @@
public boolean forceQueryableOverride;
/** {@hide} */
public int requireUserAction = USER_ACTION_UNSPECIFIED;
+ /** {@hide} */
+ public boolean keepApplicationEnabledSetting = false;
/**
* Construct parameters for a new package install session.
@@ -1899,6 +1913,7 @@
rollbackDataPolicy = source.readInt();
requireUserAction = source.readInt();
packageSource = source.readInt();
+ keepApplicationEnabledSetting = source.readBoolean();
}
/** {@hide} */
@@ -1929,6 +1944,7 @@
ret.rollbackDataPolicy = rollbackDataPolicy;
ret.requireUserAction = requireUserAction;
ret.packageSource = packageSource;
+ ret.keepApplicationEnabledSetting = keepApplicationEnabledSetting;
return ret;
}
@@ -2415,6 +2431,14 @@
this.installScenario = installScenario;
}
+ /**
+ * Request to keep the original application enabled setting. This will prevent the
+ * application from being enabled if it was previously in a disabled state.
+ */
+ public void setKeepApplicationEnabledSetting() {
+ this.keepApplicationEnabledSetting = true;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
@@ -2443,6 +2467,7 @@
pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
pw.printPair("dataLoaderParams", dataLoaderParams);
pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
+ pw.printPair("keepApplicationEnabledSetting", keepApplicationEnabledSetting);
pw.println();
}
@@ -2483,6 +2508,7 @@
dest.writeInt(rollbackDataPolicy);
dest.writeInt(requireUserAction);
dest.writeInt(packageSource);
+ dest.writeBoolean(keepApplicationEnabledSetting);
}
public static final Parcelable.Creator<SessionParams>
@@ -2684,6 +2710,9 @@
/** @hide */
public boolean isPreapprovalRequested;
+ /** @hide */
+ public boolean keepApplicationEnabledSetting;
+
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public SessionInfo() {
@@ -2737,6 +2766,7 @@
requireUserAction = source.readInt();
installerUid = source.readInt();
packageSource = source.readInt();
+ keepApplicationEnabledSetting = source.readBoolean();
}
/**
@@ -3268,6 +3298,14 @@
return installerUid;
}
+ /**
+ * Returns {@code true} if this session will keep the existing application enabled setting
+ * after installation.
+ */
+ public boolean isKeepApplicationEnabledSetting() {
+ return keepApplicationEnabledSetting;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -3317,6 +3355,7 @@
dest.writeInt(requireUserAction);
dest.writeInt(installerUid);
dest.writeInt(packageSource);
+ dest.writeBoolean(keepApplicationEnabledSetting);
}
public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 485d04d..88b5e02 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1959,6 +1959,14 @@
public static final int INSTALL_FAILED_MISSING_SPLIT = -28;
/**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package targets a deprecated SDK version.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_DEPRECATED_SDK_VERSION = -29;
+
+ /**
* Installation parse return code: this is passed in the
* {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a
* file, or does not end with the expected '.apk' extension.
@@ -9618,6 +9626,7 @@
case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA";
case INSTALL_FAILED_MISSING_SPLIT: return "INSTALL_FAILED_MISSING_SPLIT";
+ case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return "INSTALL_FAILED_DEPRECATED_SDK_VERSION";
case INSTALL_FAILED_BAD_SIGNATURE: return "INSTALL_FAILED_BAD_SIGNATURE";
case INSTALL_FAILED_WRONG_INSTALLED_VERSION: return "INSTALL_FAILED_WRONG_INSTALLED_VERSION";
case INSTALL_FAILED_PROCESS_NOT_DEFINED: return "INSTALL_FAILED_PROCESS_NOT_DEFINED";
@@ -9675,6 +9684,7 @@
case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
case INSTALL_FAILED_MISSING_SPLIT: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
case INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
default: return PackageInstaller.STATUS_FAILURE;
}
}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 3b7ed07..9e6cf62 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -308,7 +308,9 @@
* permissions:
* {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
* {@link android.Manifest.permission#BODY_SENSORS},
- * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+ * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS},
+ * or one of the {@code "android.permission.health.*"} permissions defined in the
+ * {@link android.healthconnect.HealthPermissions}.
*/
@RequiresPermission(
allOf = {
@@ -424,7 +426,7 @@
* android:name=".MySpecialForegroundService"
* android:foregroundServiceType="specialUse|foo">
* <property
- * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE""
+ * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
* android:value="foo"
* />
* </service>
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 6ce2242..ce6e1c7 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -563,6 +563,9 @@
if (applyToSize) {
inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+
+ float fontScale = inoutDm.scaledDensity / inoutDm.density;
+ inoutDm.fontScaleConverter = FontScaleConverterFactory.forScale(fontScale);
}
}
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
new file mode 100644
index 0000000..c7fdb16
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -0,0 +1,148 @@
+/*
+ * 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 android.content.res;
+
+import android.annotation.NonNull;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * "dp" dimension according to a non-linear curve.
+ *
+ * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more
+ * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen.
+ *
+ * <p>The thinking here is that large fonts are already big enough to read, but we still want to
+ * scale them slightly to preserve the visual hierarchy when compared to smaller fonts.
+ *
+ * @hide
+ */
+public class FontScaleConverter {
+ /**
+ * How close the given SP should be to a canonical SP in the array before they are considered
+ * the same for lookup purposes.
+ */
+ private static final float THRESHOLD_FOR_MATCHING_SP = 0.02f;
+
+ @VisibleForTesting
+ final float[] mFromSpValues;
+ @VisibleForTesting
+ final float[] mToDpValues;
+
+ /**
+ * Creates a lookup table for the given conversions.
+ *
+ * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
+ *
+ * <p>The arrays must be sorted ascending and monotonically increasing.
+ *
+ * @param fromSp array of dimensions in SP
+ * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
+ *
+ * @throws IllegalArgumentException if the array lengths don't match or are empty
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) {
+ if (fromSp.length != toDp.length || fromSp.length == 0) {
+ throw new IllegalArgumentException("Array lengths must match and be nonzero");
+ }
+
+ mFromSpValues = fromSp;
+ mToDpValues = toDp;
+ }
+
+ /**
+ * Convert a dimension in "sp" to "dp" using the lookup table.
+ *
+ * @hide
+ */
+ public float convertSpToDp(float sp) {
+ final float spPositive = Math.abs(sp);
+ // TODO(b/247861374): find a match at a higher index?
+ final int spRounded = Math.round(spPositive);
+ final float sign = Math.signum(sp);
+ final int index = Arrays.binarySearch(mFromSpValues, spRounded);
+ if (index >= 0 && Math.abs(spRounded - spPositive) < THRESHOLD_FOR_MATCHING_SP) {
+ // exact match, return the matching dp
+ return sign * mToDpValues[index];
+ } else {
+ // must be a value in between index and index + 1: interpolate.
+ final int lowerIndex = -(index + 1) - 1;
+
+ final float startSp;
+ final float endSp;
+ final float startDp;
+ final float endDp;
+
+ if (lowerIndex >= mFromSpValues.length - 1) {
+ // It's past our lookup table. Determine the last elements' scaling factor and use.
+ startSp = mFromSpValues[mFromSpValues.length - 1];
+ startDp = mToDpValues[mFromSpValues.length - 1];
+
+ if (startSp == 0) return 0;
+
+ final float scalingFactor = startDp / startSp;
+ return sp * scalingFactor;
+ } else if (lowerIndex == -1) {
+ // It's smaller than the smallest value in our table. Interpolate from 0.
+ startSp = 0;
+ startDp = 0;
+ endSp = mFromSpValues[0];
+ endDp = mToDpValues[0];
+ } else {
+ startSp = mFromSpValues[lowerIndex];
+ endSp = mFromSpValues[lowerIndex + 1];
+ startDp = mToDpValues[lowerIndex];
+ endDp = mToDpValues[lowerIndex + 1];
+ }
+
+ return sign * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, spPositive);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FontScaleConverter)) return false;
+ FontScaleConverter that = (FontScaleConverter) o;
+ return Arrays.equals(mFromSpValues, that.mFromSpValues)
+ && Arrays.equals(mToDpValues, that.mToDpValues);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(mFromSpValues);
+ result = 31 * result + Arrays.hashCode(mToDpValues);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "FontScaleConverter{"
+ + "fromSpValues="
+ + Arrays.toString(mFromSpValues)
+ + ", toDpValues="
+ + Arrays.toString(mToDpValues)
+ + '}';
+ }
+}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
new file mode 100644
index 0000000..c77a372
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -0,0 +1,119 @@
+/*
+ * 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 android.content.res;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Stores lookup tables for creating {@link FontScaleConverter}s at various scales.
+ *
+ * @hide
+ */
+public class FontScaleConverterFactory {
+ private static final float SCALE_KEY_MULTIPLIER = 100f;
+
+ @VisibleForTesting
+ static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>();
+
+ static {
+ // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and
+ // manually tweaked for optimum readability.
+ put(
+ /* scaleKey= */ 1.15f,
+ new FontScaleConverter(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 9.2f, 11.5f, 13.8f, 16.1f, 20.7f, 23f, 27.6f, 34.5f, 115})
+ );
+
+ put(
+ /* scaleKey= */ 1.3f,
+ new FontScaleConverter(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] {10.4f, 13f, 15.6f, 18.2f, 23.4f, 26f, 31.2f, 39f, 130})
+ );
+
+ put(
+ /* scaleKey= */ 1.5f,
+ new FontScaleConverter(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 12f, 15f, 18f, 21f, 27f, 30f, 36f, 45f, 150})
+ );
+
+ put(
+ /* scaleKey= */ 1.8f,
+ new FontScaleConverter(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] {14.4f, 18f, 21.6f, 25.2f, 32.4f, 36f, 43.2f, 54f, 180})
+ );
+
+ put(
+ /* scaleKey= */ 2f,
+ new FontScaleConverter(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 16f, 20f, 24f, 28f, 36f, 40f, 48f, 60f, 200})
+ );
+
+ }
+
+ private FontScaleConverterFactory() {}
+
+ /**
+ * Finds a matching FontScaleConverter for the given fontScale factor.
+ *
+ * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+ *
+ * @return a converter for the given scale, or null if non-linear scaling should not be used.
+ *
+ * @hide
+ */
+ @Nullable
+ public static FontScaleConverter forScale(float fontScale) {
+ if (fontScale <= 1) {
+ // We don't need non-linear curves for shrinking text or for 100%.
+ // Also, fontScale==0 should not have a curve either
+ return null;
+ }
+
+ FontScaleConverter lookupTable = get(fontScale);
+ // TODO(b/247861716): interpolate between two tables when null
+
+ return lookupTable;
+ }
+
+ private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
+ LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter);
+ }
+
+ @Nullable
+ private static FontScaleConverter get(float scaleKey) {
+ return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER));
+ }
+}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 09d24d4..c2b3769 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -434,6 +434,8 @@
// Protect against an unset fontScale.
mMetrics.scaledDensity = mMetrics.density *
(mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
+ mMetrics.fontScaleConverter =
+ FontScaleConverterFactory.forScale(mConfiguration.fontScale);
final int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index f9973a2..1efac6c 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentSender;
@@ -65,17 +66,20 @@
* credential, display a picker when multiple credentials exist, etc.
*
* @param request the request specifying type(s) of credentials to get from the user
+ * @param activity the activity used to launch any UI needed
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
public void executeGetCredential(
@NonNull GetCredentialRequest request,
+ @NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
GetCredentialResponse, CredentialManagerException> callback) {
requireNonNull(request, "request must not be null");
+ requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -88,8 +92,7 @@
try {
cancelRemote = mService.executeGetCredential(
request,
- // TODO: use a real activity instead of context.
- new GetCredentialTransport(mContext, executor, callback),
+ new GetCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -107,17 +110,20 @@
* or storing the new credential, etc.
*
* @param request the request specifying type(s) of credentials to get from the user
+ * @param activity the activity used to launch any UI needed
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
public void executeCreateCredential(
@NonNull CreateCredentialRequest request,
+ @NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
CreateCredentialResponse, CredentialManagerException> callback) {
requireNonNull(request, "request must not be null");
+ requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -129,8 +135,7 @@
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeCreateCredential(request,
- // TODO: use a real activity instead of context.
- new CreateCredentialTransport(mContext, executor, callback),
+ new CreateCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -188,14 +193,14 @@
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
- private final Context mActivityContext;
+ private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
GetCredentialResponse, CredentialManagerException> mCallback;
- private GetCredentialTransport(Context activityContext, Executor executor,
+ private GetCredentialTransport(Activity activity, Executor executor,
OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
- mActivityContext = activityContext;
+ mActivity = activity;
mExecutor = executor;
mCallback = callback;
}
@@ -203,7 +208,7 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent:"
+ pendingIntent.getIntentSender(), e);
@@ -226,14 +231,14 @@
private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
- private final Context mActivityContext;
+ private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
CreateCredentialResponse, CredentialManagerException> mCallback;
- private CreateCredentialTransport(Context activityContext, Executor executor,
+ private CreateCredentialTransport(Activity activity, Executor executor,
OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
- mActivityContext = activityContext;
+ mActivity = activity;
mExecutor = executor;
mCallback = callback;
}
@@ -241,7 +246,7 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent:"
+ pendingIntent.getIntentSender(), e);
diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java
index 19655ed..7fc282c 100644
--- a/core/java/android/hardware/usb/ParcelableUsbPort.java
+++ b/core/java/android/hardware/usb/ParcelableUsbPort.java
@@ -34,11 +34,13 @@
private final int mSupportedContaminantProtectionModes;
private final boolean mSupportsEnableContaminantPresenceProtection;
private final boolean mSupportsEnableContaminantPresenceDetection;
+ private final boolean mSupportsComplianceWarnings;
private ParcelableUsbPort(@NonNull String id, int supportedModes,
int supportedContaminantProtectionModes,
boolean supportsEnableContaminantPresenceProtection,
- boolean supportsEnableContaminantPresenceDetection) {
+ boolean supportsEnableContaminantPresenceDetection,
+ boolean supportsComplianceWarnings) {
mId = id;
mSupportedModes = supportedModes;
mSupportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -46,6 +48,8 @@
supportsEnableContaminantPresenceProtection;
mSupportsEnableContaminantPresenceDetection =
supportsEnableContaminantPresenceDetection;
+ mSupportsComplianceWarnings =
+ supportsComplianceWarnings;
}
/**
@@ -59,7 +63,8 @@
return new ParcelableUsbPort(port.getId(), port.getSupportedModes(),
port.getSupportedContaminantProtectionModes(),
port.supportsEnableContaminantPresenceProtection(),
- port.supportsEnableContaminantPresenceDetection());
+ port.supportsEnableContaminantPresenceDetection(),
+ port.supportsComplianceWarnings());
}
/**
@@ -72,7 +77,8 @@
public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) {
return new UsbPort(usbManager, mId, mSupportedModes, mSupportedContaminantProtectionModes,
mSupportsEnableContaminantPresenceProtection,
- mSupportsEnableContaminantPresenceDetection);
+ mSupportsEnableContaminantPresenceDetection,
+ mSupportsComplianceWarnings);
}
@Override
@@ -87,6 +93,7 @@
dest.writeInt(mSupportedContaminantProtectionModes);
dest.writeBoolean(mSupportsEnableContaminantPresenceProtection);
dest.writeBoolean(mSupportsEnableContaminantPresenceDetection);
+ dest.writeBoolean(mSupportsComplianceWarnings);
}
public static final @android.annotation.NonNull Creator<ParcelableUsbPort> CREATOR =
@@ -98,11 +105,13 @@
int supportedContaminantProtectionModes = in.readInt();
boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+ boolean supportsComplianceWarnings = in.readBoolean();
return new ParcelableUsbPort(id, supportedModes,
supportedContaminantProtectionModes,
supportsEnableContaminantPresenceProtection,
- supportsEnableContaminantPresenceDetection);
+ supportsEnableContaminantPresenceDetection,
+ supportsComplianceWarnings);
}
@Override
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 50dd0064..342c336 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -113,6 +113,19 @@
public static final String ACTION_USB_PORT_CHANGED =
"android.hardware.usb.action.USB_PORT_CHANGED";
+ /**
+ * Broadcast Action: A broadcast for USB compliance warning changes.
+ *
+ * This intent is sent when a port partner's
+ * (USB power source/cable/accessory) compliance warnings change state.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED =
+ "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED";
+
/**
* Activity intent sent when user attaches a USB device.
*
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 7c5a4c6..e0f9cad 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -46,6 +46,10 @@
import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_BC_1_2;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_OTHER;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -83,6 +87,7 @@
private final int mSupportedContaminantProtectionModes;
private final boolean mSupportsEnableContaminantPresenceProtection;
private final boolean mSupportsEnableContaminantPresenceDetection;
+ private final boolean mSupportsComplianceWarnings;
private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES;
/**
@@ -250,6 +255,18 @@
int supportedContaminantProtectionModes,
boolean supportsEnableContaminantPresenceProtection,
boolean supportsEnableContaminantPresenceDetection) {
+ this(usbManager, id, supportedModes, supportedContaminantProtectionModes,
+ supportsEnableContaminantPresenceProtection,
+ supportsEnableContaminantPresenceDetection,
+ false);
+ }
+
+ /** @hide */
+ public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes,
+ int supportedContaminantProtectionModes,
+ boolean supportsEnableContaminantPresenceProtection,
+ boolean supportsEnableContaminantPresenceDetection,
+ boolean supportsComplianceWarnings) {
Objects.requireNonNull(id);
Preconditions.checkFlagsArgument(supportedModes,
MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY);
@@ -262,6 +279,7 @@
supportsEnableContaminantPresenceProtection;
mSupportsEnableContaminantPresenceDetection =
supportsEnableContaminantPresenceDetection;
+ mSupportsComplianceWarnings = supportsComplianceWarnings;
}
/**
@@ -331,6 +349,21 @@
}
/**
+ * Queries USB Port to see if the port is capable of identifying
+ * non compliant USB power source/cable/accessory.
+ *
+ * @return true when the UsbPort is capable of identifying
+ * non compliant USB power
+ * source/cable/accessory.
+ * @return false otherwise.
+ */
+ @CheckResult
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ public boolean supportsComplianceWarnings() {
+ return mSupportsComplianceWarnings;
+ }
+
+ /**
* Sets the desired role combination of the port.
* <p>
* The supported role combinations depend on what is connected to the port and may be
@@ -686,6 +719,37 @@
}
/** @hide */
+ public static String complianceWarningsToString(@NonNull int[] complianceWarnings) {
+ StringBuilder complianceWarningString = new StringBuilder();
+ complianceWarningString.append("[");
+
+ if (complianceWarnings != null) {
+ for (int warning : complianceWarnings) {
+ switch (warning) {
+ case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
+ complianceWarningString.append("other, ");
+ break;
+ case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
+ complianceWarningString.append("debug accessory, ");
+ break;
+ case UsbPortStatus.COMPLIANCE_WARNING_BC_1_2:
+ complianceWarningString.append("bc12, ");
+ break;
+ case UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP:
+ complianceWarningString.append("missing rp, ");
+ break;
+ default:
+ complianceWarningString.append(String.format("Unknown(%d), ", warning));
+ break;
+ }
+ }
+ }
+
+ complianceWarningString.append("]");
+ return complianceWarningString.toString().replaceAll(", ]$", "]");
+ }
+
+ /** @hide */
public static void checkMode(int powerRole) {
Preconditions.checkArgumentInRange(powerRole, Constants.PortMode.NONE,
Constants.PortMode.NUM_MODES - 1, "portMode");
@@ -720,10 +784,12 @@
@Override
public String toString() {
return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes)
- + "supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
- + "supportsEnableContaminantPresenceProtection="
+ + ", supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
+ + ", supportsEnableContaminantPresenceProtection="
+ mSupportsEnableContaminantPresenceProtection
- + "supportsEnableContaminantPresenceDetection="
- + mSupportsEnableContaminantPresenceDetection;
+ + ", supportsEnableContaminantPresenceDetection="
+ + mSupportsEnableContaminantPresenceDetection
+ + ", supportsComplianceWarnings="
+ + mSupportsComplianceWarnings;
}
}
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 3221ec8..ed3e40d 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -16,9 +16,11 @@
package android.hardware.usb;
+import android.Manifest;
+import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -46,6 +48,7 @@
private final boolean mPowerTransferLimited;
private final @UsbDataStatus int mUsbDataStatus;
private final @PowerBrickConnectionStatus int mPowerBrickConnectionStatus;
+ private final @NonNull @ComplianceWarning int[] mComplianceWarnings;
/**
* Power role: This USB port does not have a power role.
@@ -246,6 +249,41 @@
*/
public static final int POWER_BRICK_STATUS_DISCONNECTED = 2;
+ /**
+ * Used to indicate attached sources/cables/accessories/ports
+ * that do not match the other warnings below and do not meet the
+ * requirements of specifications including but not limited to
+ * USB Type-C Cable and Connector, Universal Serial Bus
+ * Power Delivery, and Universal Serial Bus 1.x/2.0/3.x/4.0.
+ * In addition, constants introduced after the target sdk will be
+ * remapped into COMPLIANCE_WARNING_OTHER.
+ */
+ public static final int COMPLIANCE_WARNING_OTHER = 1;
+
+ /**
+ * Used to indicate Type-C port partner
+ * (cable/accessory/source) that identifies itself as debug
+ * accessory source as defined in USB Type-C Cable and
+ * Connector Specification. However, the specification states
+ * that this is meant for debug only and shall not be used for
+ * with commercial products.
+ */
+ public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2;
+
+ /**
+ * Used to indicate USB ports that does not
+ * identify itself as one of the charging port types (SDP/CDP
+ * DCP etc) as defined by Battery Charging v1.2 Specification.
+ */
+ public static final int COMPLIANCE_WARNING_BC_1_2 = 3;
+
+ /**
+ * Used to indicate Type-C sources/cables that are missing pull
+ * up resistors on the CC pins as required by USB Type-C Cable
+ * and Connector Specification.
+ */
+ public static final int COMPLIANCE_WARNING_MISSING_RP = 4;
+
@IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = {
CONTAMINANT_DETECTION_NOT_SUPPORTED,
CONTAMINANT_DETECTION_DISABLED,
@@ -275,6 +313,15 @@
@Retention(RetentionPolicy.SOURCE)
@interface UsbPortMode{}
+ @IntDef(prefix = { "COMPLIANCE_WARNING_" }, value = {
+ COMPLIANCE_WARNING_OTHER,
+ COMPLIANCE_WARNING_DEBUG_ACCESSORY,
+ COMPLIANCE_WARNING_BC_1_2,
+ COMPLIANCE_WARNING_MISSING_RP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ComplianceWarning{}
+
/** @hide */
@IntDef(prefix = { "DATA_STATUS_" }, flag = true, value = {
DATA_STATUS_UNKNOWN,
@@ -302,7 +349,8 @@
int supportedRoleCombinations, int contaminantProtectionStatus,
int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus,
boolean powerTransferLimited,
- @PowerBrickConnectionStatus int powerBrickConnectionStatus) {
+ @PowerBrickConnectionStatus int powerBrickConnectionStatus,
+ @NonNull @ComplianceWarning int[] complianceWarnings) {
mCurrentMode = currentMode;
mCurrentPowerRole = currentPowerRole;
mCurrentDataRole = currentDataRole;
@@ -312,21 +360,29 @@
mUsbDataStatus = usbDataStatus;
mPowerTransferLimited = powerTransferLimited;
mPowerBrickConnectionStatus = powerBrickConnectionStatus;
+ mComplianceWarnings = complianceWarnings;
+ }
+
+ /** @hide */
+ public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
+ int supportedRoleCombinations, int contaminantProtectionStatus,
+ int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus,
+ boolean powerTransferLimited,
+ @PowerBrickConnectionStatus int powerBrickConnectionStatus) {
+ this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations,
+ contaminantProtectionStatus, contaminantDetectionStatus,
+ usbDataStatus, powerTransferLimited, powerBrickConnectionStatus,
+ new int[] {});
}
/** @hide */
public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
int supportedRoleCombinations, int contaminantProtectionStatus,
int contaminantDetectionStatus) {
- mCurrentMode = currentMode;
- mCurrentPowerRole = currentPowerRole;
- mCurrentDataRole = currentDataRole;
- mSupportedRoleCombinations = supportedRoleCombinations;
- mContaminantProtectionStatus = contaminantProtectionStatus;
- mContaminantDetectionStatus = contaminantDetectionStatus;
- mUsbDataStatus = DATA_STATUS_UNKNOWN;
- mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN;
- mPowerTransferLimited = false;
+ this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations,
+ contaminantProtectionStatus, contaminantDetectionStatus,
+ DATA_STATUS_UNKNOWN, false, POWER_BRICK_STATUS_UNKNOWN,
+ new int[] {});
}
/**
@@ -443,6 +499,21 @@
return mPowerBrickConnectionStatus;
}
+ /**
+ * Returns non compliant reasons, if any, for the connected
+ * charger/cable/accessory/USB port.
+ *
+ * @return array including {@link #NON_COMPLIANT_REASON_DEBUG_ACCESSORY},
+ * {@link #NON_COMPLIANT_REASON_BC12},
+ * {@link #NON_COMPLIANT_REASON_MISSING_RP},
+ * or {@link #NON_COMPLIANT_REASON_TYPEC}
+ */
+ @CheckResult
+ @NonNull
+ public @ComplianceWarning int[] getComplianceWarnings() {
+ return mComplianceWarnings;
+ }
+
@NonNull
@Override
public String toString() {
@@ -460,9 +531,11 @@
+ UsbPort.usbDataStatusToString(getUsbDataStatus())
+ ", isPowerTransferLimited="
+ isPowerTransferLimited()
- +", powerBrickConnectionStatus="
+ + ", powerBrickConnectionStatus="
+ UsbPort
.powerBrickConnectionStatusToString(getPowerBrickConnectionStatus())
+ + ", complianceWarnings="
+ + UsbPort.complianceWarningsToString(getComplianceWarnings())
+ "}";
}
@@ -482,6 +555,7 @@
dest.writeInt(mUsbDataStatus);
dest.writeBoolean(mPowerTransferLimited);
dest.writeInt(mPowerBrickConnectionStatus);
+ dest.writeIntArray(mComplianceWarnings);
}
public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -497,10 +571,12 @@
int usbDataStatus = in.readInt();
boolean powerTransferLimited = in.readBoolean();
int powerBrickConnectionStatus = in.readInt();
+ @ComplianceWarning int[] complianceWarnings = in.createIntArray();
return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
contaminantDetectionStatus, usbDataStatus, powerTransferLimited,
- powerBrickConnectionStatus);
+ powerBrickConnectionStatus,
+ complianceWarnings);
}
@Override
@@ -524,6 +600,7 @@
private boolean mPowerTransferLimited;
private @UsbDataStatus int mUsbDataStatus;
private @PowerBrickConnectionStatus int mPowerBrickConnectionStatus;
+ private @ComplianceWarning int[] mComplianceWarnings;
public Builder() {
mCurrentMode = MODE_NONE;
@@ -533,6 +610,7 @@
mContaminantDetectionStatus = CONTAMINANT_DETECTION_NOT_SUPPORTED;
mUsbDataStatus = DATA_STATUS_UNKNOWN;
mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN;
+ mComplianceWarnings = new int[] {};
}
/**
@@ -619,6 +697,20 @@
}
/**
+ * Sets the non-compliant charger reasons of {@link UsbPortStatus}
+ *
+ * @return Instance of {@link Builder}
+ */
+ @NonNull
+ public Builder setComplianceWarnings(
+ @NonNull int[] complianceWarnings) {
+ mComplianceWarnings = complianceWarnings == null ? new int[] {} :
+ complianceWarnings;
+ return this;
+ }
+
+
+ /**
* Creates the {@link UsbPortStatus} object.
*/
@NonNull
@@ -626,7 +718,7 @@
UsbPortStatus status = new UsbPortStatus(mCurrentMode, mCurrentPowerRole,
mCurrentDataRole, mSupportedRoleCombinations, mContaminantProtectionStatus,
mContaminantDetectionStatus, mUsbDataStatus, mPowerTransferLimited,
- mPowerBrickConnectionStatus);
+ mPowerBrickConnectionStatus, mComplianceWarnings);
return status;
}
};
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 630ad6c..2adbbcd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -708,7 +708,7 @@
"android.settings.WIFI_SETTINGS";
/**
- * Activity Action: Show settings to allow configuration of MTE.
+ * Activity Action: Show settings to allow configuration of Advanced memory protection.
* <p>
* Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
* classes of security problems at a small runtime performance cost overhead.
@@ -720,8 +720,8 @@
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_MEMTAG_SETTINGS =
- "android.settings.MEMTAG_SETTINGS";
+ public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS =
+ "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS";
/**
* Activity Action: Show settings to allow configuration of a static IP
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 553a324..7757081 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -27,8 +27,6 @@
/**
* An action defined by the provider that intents into the provider's app for specific
* user actions.
- *
- * @hide
*/
public final class Action implements Parcelable {
/** Slice object containing display content to be displayed with this action on the UI. */
@@ -39,6 +37,13 @@
/**
* Constructs an action to be displayed on the UI.
*
+ * <p> Actions must be used for any provider related operations, such as opening the provider
+ * app, intenting straight into certain app activities like 'manage credentials', top
+ * level authentication before displaying any content etc.
+ *
+ * <p> See details on usage of {@code Action} for various actionable entries in
+ * {@link BeginCreateCredentialResponse} and {@link GetCredentialsResponse}.
+ *
* @param slice the display content to be displayed on the UI, along with this action
* @param pendingIntent the intent to be invoked when the user selects this action
*/
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
new file mode 100644
index 0000000..30cab8d
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable BeginCreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.java b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
new file mode 100644
index 0000000..1918d8c
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.service.credentials;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request for beginning a create credential request.
+ *
+ * See {@link BeginCreateCredentialResponse} for the counterpart response
+ */
+public final class BeginCreateCredentialRequest implements Parcelable {
+ private final @NonNull String mCallingPackage;
+ private final @NonNull String mType;
+ private final @NonNull Bundle mData;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @throws IllegalArgumentException If {@code callingPackage}, or {@code type} string is
+ * null or empty.
+ * @throws NullPointerException If {@code data} is null.
+ */
+ public BeginCreateCredentialRequest(@NonNull String callingPackage,
+ @NonNull String type, @NonNull Bundle data) {
+ mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage,
+ "callingPackage must not be null or empty");
+ mType = Preconditions.checkStringNotEmpty(type,
+ "type must not be null or empty");
+ mData = Objects.requireNonNull(data, "data must not be null");
+ }
+
+ private BeginCreateCredentialRequest(@NonNull Parcel in) {
+ mCallingPackage = in.readString8();
+ mType = in.readString8();
+ mData = in.readBundle(Bundle.class.getClassLoader());
+ }
+
+ public static final @NonNull Creator<BeginCreateCredentialRequest> CREATOR =
+ new Creator<BeginCreateCredentialRequest>() {
+ @Override
+ public BeginCreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+ return new BeginCreateCredentialRequest(in);
+ }
+
+ @Override
+ public BeginCreateCredentialRequest[] newArray(int size) {
+ return new BeginCreateCredentialRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mCallingPackage);
+ dest.writeString8(mType);
+ dest.writeBundle(mData);
+ }
+
+ /** Returns the calling package of the calling app. */
+ @NonNull
+ public String getCallingPackage() {
+ return mCallingPackage;
+ }
+
+ /** Returns the type of the credential to be created. */
+ @NonNull
+ public String getType() {
+ return mType;
+ }
+
+ /** Returns the data to be used while resolving the credential to create. */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
similarity index 93%
rename from core/java/android/service/credentials/CreateCredentialResponse.aidl
rename to core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
index 73c9147..d2a1408 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
@@ -16,4 +16,4 @@
package android.service.credentials;
-parcelable CreateCredentialResponse;
+parcelable BeginCreateCredentialResponse;
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
new file mode 100644
index 0000000..022678e
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -0,0 +1,150 @@
+/*
+ * 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 android.service.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response to a {@link BeginCreateCredentialRequest}.
+ */
+public final class BeginCreateCredentialResponse implements Parcelable {
+ private final @NonNull List<CreateEntry> mCreateEntries;
+ private final @Nullable CreateEntry mRemoteCreateEntry;
+
+ private BeginCreateCredentialResponse(@NonNull Parcel in) {
+ List<CreateEntry> createEntries = new ArrayList<>();
+ in.readTypedList(createEntries, CreateEntry.CREATOR);
+ mCreateEntries = createEntries;
+ mRemoteCreateEntry = in.readTypedObject(CreateEntry.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mCreateEntries);
+ dest.writeTypedObject(mRemoteCreateEntry, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<BeginCreateCredentialResponse> CREATOR =
+ new Creator<BeginCreateCredentialResponse>() {
+ @Override
+ public BeginCreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+ return new BeginCreateCredentialResponse(in);
+ }
+
+ @Override
+ public BeginCreateCredentialResponse[] newArray(int size) {
+ return new BeginCreateCredentialResponse[size];
+ }
+ };
+
+ /* package-private */ BeginCreateCredentialResponse(
+ @NonNull List<CreateEntry> createEntries,
+ @Nullable CreateEntry remoteCreateEntry) {
+ this.mCreateEntries = createEntries;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mCreateEntries);
+ this.mRemoteCreateEntry = remoteCreateEntry;
+ }
+
+ /** Returns the list of create entries to be displayed on the UI. */
+ public @NonNull List<CreateEntry> getCreateEntries() {
+ return mCreateEntries;
+ }
+
+ /** Returns the remote create entry to be displayed on the UI. */
+ public @Nullable CreateEntry getRemoteCreateEntry() {
+ return mRemoteCreateEntry;
+ }
+
+ /**
+ * A builder for {@link BeginCreateCredentialResponse}
+ */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ public static final class Builder {
+ private @NonNull List<CreateEntry> mCreateEntries = new ArrayList<>();
+ private @Nullable CreateEntry mRemoteCreateEntry;
+
+ /**
+ * Sets the list of create entries to be shown on the UI.
+ *
+ * @throws IllegalArgumentException If {@code createEntries} is empty.
+ * @throws NullPointerException If {@code createEntries} is null, or any of its elements
+ * are null.
+ */
+ public @NonNull Builder setCreateEntries(@NonNull List<CreateEntry> createEntries) {
+ Preconditions.checkCollectionNotEmpty(createEntries, "createEntries");
+ mCreateEntries = Preconditions.checkCollectionElementsNotNull(
+ createEntries, "createEntries");
+ return this;
+ }
+
+ /**
+ * Adds an entry to the list of create entries to be shown on the UI.
+ *
+ * @throws NullPointerException If {@code createEntry} is null.
+ */
+ public @NonNull Builder addCreateEntry(@NonNull CreateEntry createEntry) {
+ mCreateEntries.add(Objects.requireNonNull(createEntry));
+ return this;
+ }
+
+ /**
+ * Sets a remote create entry to be shown on the UI. Provider must set this entry if they
+ * wish to create the credential on a different device.
+ *
+ * <p> When constructing the {@link CreateEntry} object, the {@code pendingIntent} must be
+ * set such that it leads to an activity that can provide UI to fulfill the request on
+ * a remote device. When user selects this {@code remoteCreateEntry}, the system will
+ * invoke the {@code pendingIntent} set on the {@link CreateEntry}.
+ *
+ * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+ * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+ * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESULT} key should be populated
+ * with a {@link android.credentials.CreateCredentialResponse} object.
+ */
+ public @NonNull Builder setRemoteCreateEntry(@Nullable CreateEntry remoteCreateEntry) {
+ mRemoteCreateEntry = remoteCreateEntry;
+ return this;
+ }
+
+ /**
+ * Builds a new instance of {@link BeginCreateCredentialResponse}.
+ *
+ * @throws NullPointerException If {@code createEntries} is null.
+ * @throws IllegalArgumentException If {@code createEntries} is empty.
+ */
+ public @NonNull BeginCreateCredentialResponse build() {
+ Preconditions.checkCollectionNotEmpty(mCreateEntries, "createEntries must "
+ + "not be null, or empty");
+ return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.aidl b/core/java/android/service/credentials/CreateCredentialRequest.aidl
deleted file mode 100644
index eb7fba9..0000000
--- a/core/java/android/service/credentials/CreateCredentialRequest.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.service.credentials;
-
-parcelable CreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
index e6da349..aee85ab 100644
--- a/core/java/android/service/credentials/CreateCredentialRequest.java
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -27,8 +27,6 @@
/**
* Request for creating a credential.
- *
- * @hide
*/
public final class CreateCredentialRequest implements Parcelable {
private final @NonNull String mCallingPackage;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
deleted file mode 100644
index f69dca8..0000000
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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 android.service.credentials;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Response to a {@link CreateCredentialRequest}.
- *
- * @hide
- */
-public final class CreateCredentialResponse implements Parcelable {
- private final @NonNull List<SaveEntry> mSaveEntries;
- private final @Nullable Action mRemoteSaveEntry;
- //TODO : Add actions if needed
-
- private CreateCredentialResponse(@NonNull Parcel in) {
- List<SaveEntry> saveEntries = new ArrayList<>();
- in.readTypedList(saveEntries, SaveEntry.CREATOR);
- mSaveEntries = saveEntries;
- mRemoteSaveEntry = in.readTypedObject(Action.CREATOR);
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedList(mSaveEntries);
- dest.writeTypedObject(mRemoteSaveEntry, flags);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public static final @NonNull Creator<CreateCredentialResponse> CREATOR =
- new Creator<CreateCredentialResponse>() {
- @Override
- public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
- return new CreateCredentialResponse(in);
- }
-
- @Override
- public CreateCredentialResponse[] newArray(int size) {
- return new CreateCredentialResponse[size];
- }
- };
-
- /* package-private */ CreateCredentialResponse(
- @NonNull List<SaveEntry> saveEntries,
- @Nullable Action remoteSaveEntry) {
- this.mSaveEntries = saveEntries;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mSaveEntries);
- this.mRemoteSaveEntry = remoteSaveEntry;
- }
-
- /** Returns the list of save entries to be displayed on the UI. */
- public @NonNull List<SaveEntry> getSaveEntries() {
- return mSaveEntries;
- }
-
- /** Returns the remote save entry to be displayed on the UI. */
- public @NonNull Action getRemoteSaveEntry() {
- return mRemoteSaveEntry;
- }
-
- /**
- * A builder for {@link CreateCredentialResponse}
- */
- @SuppressWarnings("WeakerAccess")
- public static final class Builder {
- private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
- private @Nullable Action mRemoteSaveEntry;
-
- /**
- * Sets the list of save entries to be shown on the UI.
- *
- * @throws IllegalArgumentException If {@code saveEntries} is empty.
- * @throws NullPointerException If {@code saveEntries} is null, or any of its elements
- * are null.
- */
- public @NonNull Builder setSaveEntries(@NonNull List<SaveEntry> saveEntries) {
- Preconditions.checkCollectionNotEmpty(saveEntries, "saveEntries");
- mSaveEntries = Preconditions.checkCollectionElementsNotNull(
- saveEntries, "saveEntries");
- return this;
- }
-
- /**
- * Adds an entry to the list of save entries to be shown on the UI.
- *
- * @throws NullPointerException If {@code saveEntry} is null.
- */
- public @NonNull Builder addSaveEntry(@NonNull SaveEntry saveEntry) {
- mSaveEntries.add(Objects.requireNonNull(saveEntry));
- return this;
- }
-
- /**
- * Sets a remote save entry to be shown on the UI.
- */
- public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) {
- mRemoteSaveEntry = remoteSaveEntry;
- return this;
- }
-
- /**
- * Builds the instance.
- *
- * @throws IllegalArgumentException If {@code saveEntries} is empty.
- */
- public @NonNull CreateCredentialResponse build() {
- Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
- + "not be empty");
- return new CreateCredentialResponse(
- mSaveEntries,
- mRemoteSaveEntry);
- }
- }
-}
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/CreateEntry.java
similarity index 77%
rename from core/java/android/service/credentials/SaveEntry.java
rename to core/java/android/service/credentials/CreateEntry.java
index 55ff6ff..eb25e25 100644
--- a/core/java/android/service/credentials/SaveEntry.java
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -25,27 +25,25 @@
/**
* An entry to be shown on the UI. This entry represents where the credential to be created will
* be stored. Examples include user's account, family group etc.
- *
- * @hide
*/
-public final class SaveEntry implements Parcelable {
+public final class CreateEntry implements Parcelable {
private final @NonNull Slice mSlice;
private final @NonNull PendingIntent mPendingIntent;
- private SaveEntry(@NonNull Parcel in) {
+ private CreateEntry(@NonNull Parcel in) {
mSlice = in.readTypedObject(Slice.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
}
- public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
+ public static final @NonNull Creator<CreateEntry> CREATOR = new Creator<CreateEntry>() {
@Override
- public SaveEntry createFromParcel(@NonNull Parcel in) {
- return new SaveEntry(in);
+ public CreateEntry createFromParcel(@NonNull Parcel in) {
+ return new CreateEntry(in);
}
@Override
- public SaveEntry[] newArray(int size) {
- return new SaveEntry[size];
+ public CreateEntry[] newArray(int size) {
+ return new CreateEntry[size];
}
};
@@ -61,12 +59,12 @@
}
/**
- * Constructs a save entry to be displayed on the UI.
+ * Constructs a CreateEntry to be displayed on the UI.
*
* @param slice the display content to be displayed on the UI, along with this entry
* @param pendingIntent the intent to be invoked when the user selects this entry
*/
- public SaveEntry(
+ public CreateEntry(
@NonNull Slice slice,
@NonNull PendingIntent pendingIntent) {
this.mSlice = slice;
@@ -77,12 +75,12 @@
NonNull.class, null, mPendingIntent);
}
- /** Returns the content to be displayed with this save entry on the UI. */
+ /** Returns the content to be displayed with this create entry on the UI. */
public @NonNull Slice getSlice() {
return mSlice;
}
- /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
+ /** Returns the pendingIntent to be invoked when this create entry on the UI is selectcd. */
public @NonNull PendingIntent getPendingIntent() {
return mPendingIntent;
}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 98c537a..941db02b 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -31,8 +31,6 @@
/**
* A credential entry that is displayed on the account selector UI. Each entry corresponds to
* something that the user can select.
- *
- * @hide
*/
public final class CredentialEntry implements Parcelable {
/** The type of the credential entry to be shown on the UI. */
@@ -145,61 +143,67 @@
private boolean mAutoSelectAllowed = false;
/**
- * Builds the instance.
+ * Creates a builder for a {@link CredentialEntry} that should invoke a
+ * {@link PendingIntent} when selected by the user.
+ *
+ * <p>The {@code pendingIntent} can be used to launch activities that require some user
+ * engagement before getting the credential corresponding to this entry,
+ * e.g. authentication, confirmation etc.
+ * Once the activity fulfills the required user engagement, the
+ * {@link android.app.Activity} result should be set to
+ * {@link android.app.Activity#RESULT_OK}, and the
+ * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} must be set with a
+ * {@link Credential} object.
+ *
* @param type the type of credential underlying this credential entry
* @param slice the content to be displayed with this entry on the UI
+ * @param pendingIntent the pendingIntent to be invoked when this entry is selected by the
+ * user
*
- * @throws IllegalArgumentException If {@code type} is null or empty.
- * @throws NullPointerException If {@code slice} is null.
+ * @throws NullPointerException If {@code slice}, or {@code pendingIntent} is null.
+ * @throws IllegalArgumentException If {@code type} is null or empty, or if
+ * {@code pendingIntent} was not created with {@link PendingIntent#getActivity}
+ * or {@link PendingIntent#getActivities}.
*/
- public Builder(@NonNull String type, @NonNull Slice slice) {
+ public Builder(@NonNull String type, @NonNull Slice slice,
+ @NonNull PendingIntent pendingIntent) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+ "null, or empty");
mSlice = Objects.requireNonNull(slice,
"slice must not be null");
+ mPendingIntent = Objects.requireNonNull(pendingIntent,
+ "pendingIntent must not be null");
+ if (!mPendingIntent.isActivity()) {
+ throw new IllegalStateException("Pending intent must start an activity");
+ }
}
/**
- * Sets the pendingIntent to be invoked if the user selects this entry.
+ * Creates a builder for a {@link CredentialEntry} that contains a {@link Credential},
+ * and does not require further action.
+ * @param type the type of credential underlying this credential entry
+ * @param slice the content to be displayed with this entry on the UI
+ * @param credential the credential to be returned to the client app, when this entry is
+ * selected by the user
*
- * The pending intent can be used to launch activities that require some user engagement
- * before getting the credential corresponding to this entry, e.g. authentication,
- * confirmation etc.
- * Once the activity fulfills the required user engagement, a {@link Credential} object
- * must be returned as an extra on activity finish.
- *
- * @throws IllegalStateException If {@code credential} is already set. Must either set the
- * {@code credential}, or the {@code pendingIntent}.
+ * @throws IllegalArgumentException If {@code type} is null or empty.
+ * @throws NullPointerException If {@code slice}, or {@code credential} is null.
*/
- public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
- if (pendingIntent != null) {
- Preconditions.checkState(mCredential == null,
- "credential is already set. Cannot set both the pendingIntent "
- + "and the credential");
- }
- mPendingIntent = pendingIntent;
- return this;
- }
-
- /**
- * Sets the credential to be used, if the user selects this entry.
- *
- * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set
- * the {@code pendingIntent}, or the {@code credential}.
- */
- public @NonNull Builder setCredential(@Nullable Credential credential) {
- if (credential != null) {
- Preconditions.checkState(mPendingIntent == null,
- "pendingIntent is already set. Cannot set both the "
- + "pendingIntent and the credential");
- }
- mCredential = credential;
- return this;
+ public Builder(@NonNull String type, @NonNull Slice slice, @NonNull Credential credential) {
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+ + "null, or empty");
+ mSlice = Objects.requireNonNull(slice,
+ "slice must not be null");
+ mCredential = Objects.requireNonNull(credential,
+ "credential must not be null");
}
/**
* Sets whether the entry is allowed to be auto selected by the framework.
* The default value is set to false.
+ *
+ * <p> The entry is only auto selected if it is the only entry on the user selector,
+ * AND the developer has also enabled auto select, while building the request.
*/
public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) {
mAutoSelectAllowed = autoSelectAllowed;
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
index 06f0052..02b7443 100644
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ b/core/java/android/service/credentials/CredentialProviderException.java
@@ -24,8 +24,6 @@
/**
* Contains custom exceptions to be used by credential providers on failure.
- *
- * @hide
*/
public class CredentialProviderException extends Exception {
public static final int ERROR_UNKNOWN = 0;
@@ -59,6 +57,11 @@
@Retention(RetentionPolicy.SOURCE)
public @interface CredentialProviderError { }
+ public CredentialProviderException(@CredentialProviderError int errorCode,
+ @NonNull String message, @NonNull Throwable cause) {
+ super(message, cause);
+ mErrorCode = errorCode;
+ }
public CredentialProviderException(@CredentialProviderError int errorCode,
@NonNull String message) {
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 24b7c3c..32646e6 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -37,30 +37,63 @@
/**
* Service to be extended by credential providers, in order to return user credentials
* to the framework.
- *
- * @hide
*/
public abstract class CredentialProviderService extends Service {
- /** Extra to be used by provider to populate the credential when ending the activity started
- * through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
- public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
- "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
-
- /** Extra to be used by provider to populate the {@link CredentialsDisplayContent} when
- * an authentication action entry is selected. **/
- public static final String EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT =
- "android.service.credentials.extra.GET_CREDENTIALS_DISPLAY_CONTENT";
+ /**
+ * Intent extra: The {@link android.credentials.CreateCredentialRequest} attached with
+ * the {@code pendingIntent} that is invoked when the user selects a {@link CreateEntry}
+ * returned as part of the {@link BeginCreateCredentialResponse}
+ *
+ * <p>
+ * Type: {@link android.credentials.CreateCredentialRequest}
+ */
+ public static final String EXTRA_CREATE_CREDENTIAL_REQUEST =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
/**
- * Provider must read the value against this extra to receive the complete create credential
- * request parameters, when a pending intent is launched.
+ * Intent extra: The result of a create flow operation, to be set on finish of the
+ * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+ * a {@link CreateEntry}.
+ *
+ * <p>
+ * Type: {@link android.credentials.CreateCredentialResponse}
*/
- public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
- "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
+ public static final String EXTRA_CREATE_CREDENTIAL_RESULT =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
- /** Extra to be used by the provider when setting the credential result. */
- public static final String EXTRA_GET_CREDENTIAL =
- "android.service.credentials.extra.GET_CREDENTIAL";
+ /**
+ * Intent extra: The result of a get credential flow operation, to be set on finish of the
+ * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+ * a {@link CredentialEntry}.
+ *
+ * <p>
+ * Type: {@link android.credentials.Credential}
+ */
+ public static final String EXTRA_CREDENTIAL_RESULT =
+ "android.service.credentials.extra.CREDENTIAL_RESULT";
+
+ /**
+ * Intent extra: The result of an authentication flow, to be set on finish of the
+ * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on
+ * a {@link GetCredentialsResponse}. This result should contain the actual content, including
+ * credential entries and action entries, to be shown on the selector.
+ *
+ * <p>
+ * Type: {@link CredentialsResponseContent}
+ */
+ public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT =
+ "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+
+ /**
+ * Intent extra: The error result of any {@link android.app.PendingIntent} flow, to be set
+ * on finish of the corresponding {@link android.app.Activity}. This result should contain an
+ * error code, representing the error encountered by the provider.
+ *
+ * <p>
+ * Type: {@link String}
+ */
+ public static final String EXTRA_ERROR =
+ "android.service.credentials.extra.ERROR";
private static final String TAG = "CredProviderService";
@@ -129,20 +162,21 @@
}
@Override
- public ICancellationSignal onCreateCredential(CreateCredentialRequest request,
- ICreateCredentialCallback callback) {
+ public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request,
+ IBeginCreateCredentialCallback callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(callback);
ICancellationSignal transport = CancellationSignal.createTransport();
mHandler.sendMessage(obtainMessage(
- CredentialProviderService::onCreateCredential,
+ CredentialProviderService::onBeginCreateCredential,
CredentialProviderService.this, request,
CancellationSignal.fromTransport(transport),
- new OutcomeReceiver<CreateCredentialResponse, CredentialProviderException>() {
+ new OutcomeReceiver<
+ BeginCreateCredentialResponse, CredentialProviderException>() {
@Override
- public void onResult(CreateCredentialResponse result) {
+ public void onResult(BeginCreateCredentialResponse result) {
try {
callback.onSuccess(result);
} catch (RemoteException e) {
@@ -182,8 +216,8 @@
* the android system.
* @param callback Object used to relay the response of the credential creation request.
*/
- public abstract void onCreateCredential(@NonNull CreateCredentialRequest request,
+ public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
@NonNull CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<CreateCredentialResponse,
+ @NonNull OutcomeReceiver<BeginCreateCredentialResponse,
CredentialProviderException> callback);
}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsResponseContent.java
similarity index 64%
rename from core/java/android/service/credentials/CredentialsDisplayContent.java
rename to core/java/android/service/credentials/CredentialsResponseContent.java
index 4b23800..32cab50 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsResponseContent.java
@@ -28,12 +28,10 @@
import java.util.Objects;
/**
- * Content to be displayed on the account selector UI, including credential entries,
- * actions etc.
- *
- * @hide
+ * The content to be displayed on the account selector UI, including credential entries,
+ * actions etc. Returned as part of {@link GetCredentialsResponse}
*/
-public final class CredentialsDisplayContent implements Parcelable {
+public final class CredentialsResponseContent implements Parcelable {
/** List of credential entries to be displayed on the UI. */
private final @NonNull List<CredentialEntry> mCredentialEntries;
@@ -41,36 +39,36 @@
private final @NonNull List<Action> mActions;
/** Remote credential entry to get the response from a different device. */
- private final @Nullable Action mRemoteCredentialEntry;
+ private final @Nullable CredentialEntry mRemoteCredentialEntry;
- private CredentialsDisplayContent(@NonNull List<CredentialEntry> credentialEntries,
+ private CredentialsResponseContent(@NonNull List<CredentialEntry> credentialEntries,
@NonNull List<Action> actions,
- @Nullable Action remoteCredentialEntry) {
+ @Nullable CredentialEntry remoteCredentialEntry) {
mCredentialEntries = credentialEntries;
mActions = actions;
mRemoteCredentialEntry = remoteCredentialEntry;
}
- private CredentialsDisplayContent(@NonNull Parcel in) {
+ private CredentialsResponseContent(@NonNull Parcel in) {
List<CredentialEntry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
mCredentialEntries = credentialEntries;
List<Action> actions = new ArrayList<>();
in.readTypedList(actions, Action.CREATOR);
mActions = actions;
- mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR);
+ mRemoteCredentialEntry = in.readTypedObject(CredentialEntry.CREATOR);
}
- public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
- new Creator<CredentialsDisplayContent>() {
+ public static final @NonNull Creator<CredentialsResponseContent> CREATOR =
+ new Creator<CredentialsResponseContent>() {
@Override
- public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) {
- return new CredentialsDisplayContent(in);
+ public CredentialsResponseContent createFromParcel(@NonNull Parcel in) {
+ return new CredentialsResponseContent(in);
}
@Override
- public CredentialsDisplayContent[] newArray(int size) {
- return new CredentialsDisplayContent[size];
+ public CredentialsResponseContent[] newArray(int size) {
+ return new CredentialsResponseContent[size];
}
};
@@ -103,22 +101,34 @@
/**
* Returns the remote credential entry to be displayed on the UI.
*/
- public @Nullable Action getRemoteCredentialEntry() {
+ public @Nullable CredentialEntry getRemoteCredentialEntry() {
return mRemoteCredentialEntry;
}
/**
- * Builds an instance of {@link CredentialsDisplayContent}.
+ * Builds an instance of {@link CredentialsResponseContent}.
*/
public static final class Builder {
private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
private List<Action> mActions = new ArrayList<>();
- private Action mRemoteCredentialEntry;
+ private CredentialEntry mRemoteCredentialEntry;
/**
- * Sets the remote credential entry to be displayed on the UI.
+ * Sets a remote credential entry to be shown on the UI. Provider must set this if they
+ * wish to get the credential from a different device.
+ *
+ * <p> When constructing the {@link CredentialEntry} object, the {@code pendingIntent}
+ * must be set such that it leads to an activity that can provide UI to fulfill the request
+ * on a remote device. When user selects this {@code remoteCredentialEntry}, the system will
+ * invoke the {@code pendingIntent} set on the {@link CredentialEntry}.
+ *
+ * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+ * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+ * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} key should be populated
+ * with a {@link android.credentials.Credential} object.
*/
- public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
+ public @NonNull Builder setRemoteCredentialEntry(@Nullable CredentialEntry
+ remoteCredentialEntry) {
mRemoteCredentialEntry = remoteCredentialEntry;
return this;
}
@@ -138,6 +148,11 @@
* Adds an {@link Action} to the list of actions to be displayed on
* the UI.
*
+ * <p> An {@code action} must be used for independent user actions,
+ * such as opening the app, intenting directly into a certain app activity etc. The
+ * {@code pendingIntent} set with the {@code action} must invoke the corresponding
+ * activity.
+ *
* @throws NullPointerException If {@code action} is null.
*/
public @NonNull Builder addAction(@NonNull Action action) {
@@ -175,17 +190,16 @@
/**
* Builds a {@link GetCredentialsResponse} instance.
*
- * @throws NullPointerException If {@code credentialEntries} is null.
- * @throws IllegalStateException if both {@code credentialEntries} and
- * {@code actions} are empty.
+ * @throws IllegalStateException if {@code credentialEntries}, {@code actions}
+ * and {@code remoteCredentialEntry} are all null or empty.
*/
- public @NonNull CredentialsDisplayContent build() {
+ public @NonNull CredentialsResponseContent build() {
if (mCredentialEntries != null && mCredentialEntries.isEmpty()
- && mActions != null && mActions.isEmpty()) {
+ && mActions != null && mActions.isEmpty() && mRemoteCredentialEntry == null) {
throw new IllegalStateException("credentialEntries and actions must not both "
+ "be empty");
}
- return new CredentialsDisplayContent(mCredentialEntries, mActions,
+ return new CredentialsResponseContent(mCredentialEntries, mActions,
mRemoteCredentialEntry);
}
}
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index 03ba20e..9052b54 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -29,8 +29,6 @@
/**
* Request for getting user's credentials from a given credential provider.
- *
- * @hide
*/
public final class GetCredentialsRequest implements Parcelable {
/** Calling package of the app requesting for credentials. */
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java
index 979a699..5263141 100644
--- a/core/java/android/service/credentials/GetCredentialsResponse.java
+++ b/core/java/android/service/credentials/GetCredentialsResponse.java
@@ -26,12 +26,10 @@
/**
* Response from a credential provider, containing credential entries and other associated
* data to be shown on the account selector UI.
- *
- * @hide
*/
public final class GetCredentialsResponse implements Parcelable {
/** Content to be used for the UI. */
- private final @Nullable CredentialsDisplayContent mCredentialsDisplayContent;
+ private final @Nullable CredentialsResponseContent mCredentialsResponseContent;
/**
* Authentication action that must be launched and completed before showing any content
@@ -40,11 +38,17 @@
private final @Nullable Action mAuthenticationAction;
/**
- * Creates a {@link GetCredentialsRequest} instance with an authentication action set.
+ * Creates a {@link GetCredentialsResponse} instance with an authentication {@link Action} set.
* Providers must use this method when no content can be shown before authentication.
*
- * Once the authentication action activity is launched, and the user is authenticated, providers
- * should create another response with {@link CredentialsDisplayContent} using
+ * <p> When the user selects this {@code authenticationAction}, the system invokes the
+ * corresponding {@code pendingIntent}. Once the authentication flow is complete,
+ * the {@link android.app.Activity} result should be set
+ * to {@link android.app.Activity#RESULT_OK} and the
+ * {@link CredentialProviderService#EXTRA_GET_CREDENTIALS_CONTENT_RESULT} extra should be set
+ * with a fully populated {@link CredentialsResponseContent} object.
+ * the authentication action activity is launched, and the user is authenticated, providers
+ * should create another response with {@link CredentialsResponseContent} using
* {@code createWithDisplayContent}, and add that response to the result of the authentication
* activity.
*
@@ -58,27 +62,27 @@
}
/**
- * Creates a {@link GetCredentialsRequest} instance with display content to be shown on the UI.
+ * Creates a {@link GetCredentialsRequest} instance with content to be shown on the UI.
* Providers must use this method when there is content to be shown without top level
- * authentication required.
+ * authentication required, including credential entries, action entries or a remote entry,
*
- * @throws NullPointerException If {@code credentialsDisplayContent} is null.
+ * @throws NullPointerException If {@code credentialsResponseContent} is null.
*/
- public static @NonNull GetCredentialsResponse createWithDisplayContent(
- @NonNull CredentialsDisplayContent credentialsDisplayContent) {
- Objects.requireNonNull(credentialsDisplayContent,
- "credentialsDisplayContent must not be null");
- return new GetCredentialsResponse(credentialsDisplayContent, null);
+ public static @NonNull GetCredentialsResponse createWithResponseContent(
+ @NonNull CredentialsResponseContent credentialsResponseContent) {
+ Objects.requireNonNull(credentialsResponseContent,
+ "credentialsResponseContent must not be null");
+ return new GetCredentialsResponse(credentialsResponseContent, null);
}
- private GetCredentialsResponse(@Nullable CredentialsDisplayContent credentialsDisplayContent,
+ private GetCredentialsResponse(@Nullable CredentialsResponseContent credentialsResponseContent,
@Nullable Action authenticationAction) {
- mCredentialsDisplayContent = credentialsDisplayContent;
+ mCredentialsResponseContent = credentialsResponseContent;
mAuthenticationAction = authenticationAction;
}
private GetCredentialsResponse(@NonNull Parcel in) {
- mCredentialsDisplayContent = in.readTypedObject(CredentialsDisplayContent.CREATOR);
+ mCredentialsResponseContent = in.readTypedObject(CredentialsResponseContent.CREATOR);
mAuthenticationAction = in.readTypedObject(Action.CREATOR);
}
@@ -102,23 +106,23 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedObject(mCredentialsDisplayContent, flags);
+ dest.writeTypedObject(mCredentialsResponseContent, flags);
dest.writeTypedObject(mAuthenticationAction, flags);
}
/**
- * Returns the authentication action to be invoked before any other content
- * can be shown to the user.
+ * If this response represents a top level authentication action, returns the authentication
+ * action to be invoked before any other content can be shown to the user.
*/
public @Nullable Action getAuthenticationAction() {
return mAuthenticationAction;
}
/**
- * Returns the credentialDisplayContent that does not require authentication, and
- * can be shown to the user on the account selector UI.
+ * Returns the actual content to be displayed on the selector, if this response does not
+ * require any top level authentication.
*/
- public @Nullable CredentialsDisplayContent getCredentialsDisplayContent() {
- return mCredentialsDisplayContent;
+ public @Nullable CredentialsResponseContent getCredentialsResponseContent() {
+ return mCredentialsResponseContent;
}
}
diff --git a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
new file mode 100644
index 0000000..ec0bc36
--- /dev/null
+++ b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.BeginCreateCredentialResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface IBeginCreateCredentialCallback {
+ void onSuccess(in BeginCreateCredentialResponse request);
+ void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICreateCredentialCallback.aidl b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
deleted file mode 100644
index 4cc76a4..0000000
--- a/core/java/android/service/credentials/ICreateCredentialCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.service.credentials;
-
-import android.service.credentials.CreateCredentialResponse;
-
-/**
- * Interface from the system to a credential provider service.
- *
- * @hide
- */
-oneway interface ICreateCredentialCallback {
- void onSuccess(in CreateCredentialResponse request);
- void onFailure(int errorCode, in CharSequence message);
-}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index c21cefa..b9eb3ed 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -18,9 +18,9 @@
import android.os.ICancellationSignal;
import android.service.credentials.GetCredentialsRequest;
-import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.IGetCredentialsCallback;
-import android.service.credentials.ICreateCredentialCallback;
+import android.service.credentials.IBeginCreateCredentialCallback;
import android.os.ICancellationSignal;
/**
@@ -30,5 +30,5 @@
*/
interface ICredentialProviderService {
ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
- ICancellationSignal onCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
+ ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 09d0fc5..e5c9adb 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
+import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
@@ -725,7 +726,7 @@
*/
@Deprecated
@RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
- public void onCallStateChanged(@Annotation.CallState int state, String phoneNumber) {
+ public void onCallStateChanged(@CallState int state, String phoneNumber) {
// default implementation empty
}
@@ -1568,48 +1569,12 @@
() -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
}
- public void onCallStatesChanged(List<CallState> callStateList) {
+ public void onCallAttributesChanged(CallAttributes callAttributes) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
- if (callStateList == null) return;
- CallAttributes ca;
- if (callStateList.isEmpty()) {
- ca = new CallAttributes(
- new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
- } else {
- int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- for (CallState cs : callStateList) {
- switch (cs.getCallClassification()) {
- case CallState.CALL_CLASSIFICATION_FOREGROUND:
- foregroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_BACKGROUND:
- backgroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_RINGING:
- ringingCallState = cs.getCallState();
- break;
- default:
- break;
- }
- }
- ca = new CallAttributes(
- new PreciseCallState(
- foregroundCallState, backgroundCallState, ringingCallState,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- callStateList.get(0).getNetworkType(),
- callStateList.get(0).getCallQuality());
- }
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(
- () -> psl.onCallAttributesChanged(ca)));
+ () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 54c4b668..e8960b8 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,7 +27,6 @@
import android.os.Build;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
@@ -63,7 +62,7 @@
* appropriate sub-interfaces.
*/
public class TelephonyCallback {
- private static final String LOG_TAG = "TelephonyCallback";
+
/**
* Experiment flag to set the per-pid registration limit for TelephonyCallback
*
@@ -1333,9 +1332,7 @@
@SystemApi
public interface CallAttributesListener {
/**
- * Callback invoked when the call attributes changes on the active call on the registered
- * subscription. If the user swaps between a foreground and background call the call
- * attributes will be reported for the active call only.
+ * Callback invoked when the call attributes changes on the registered subscription.
* Note, the registration subscription ID comes from {@link TelephonyManager} object
* which registers TelephonyCallback by
* {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
@@ -1349,77 +1346,9 @@
* {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
*
* @param callAttributes the call attributes
- * @deprecated Use onCallStatesChanged({@link List<CallState>}) to get each of call
- * state for all ongoing calls on the subscription.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- @Deprecated
- default void onCallAttributesChanged(@NonNull CallAttributes callAttributes) {
- Log.w(LOG_TAG, "onCallAttributesChanged(List<CallAttributes>) should be "
- + "overridden.");
- }
-
- /**
- * Callback invoked when the call attributes changes on the ongoing calls on the registered
- * subscription. If there are 1 foreground and 1 background call, Two {@link CallState}
- * will be passed.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers TelephonyCallback by
- * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- * In the event that there are no active(state is not
- * {@link PreciseCallState#PRECISE_CALL_STATE_IDLE}) calls, this API will report empty list.
- *
- * The calling app should have carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
- * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
- *
- * @param callStateList the list of call states for each ongoing call. If there are
- * a active call and a holding call, 1 call attributes for
- * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} and another
- * for {@link PreciseCallState#PRECISE_CALL_STATE_HOLDING}
- * will be in this list.
- */
- // Added as default for backward compatibility
- @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- default void onCallStatesChanged(@NonNull List<CallState> callStateList) {
- if (callStateList.size() > 0) {
- int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- for (CallState cs : callStateList) {
- switch (cs.getCallClassification()) {
- case CallState.CALL_CLASSIFICATION_FOREGROUND:
- foregroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_BACKGROUND:
- backgroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_RINGING:
- ringingCallState = cs.getCallState();
- break;
- default:
- break;
- }
- }
- onCallAttributesChanged(new CallAttributes(
- new PreciseCallState(
- ringingCallState, foregroundCallState, backgroundCallState,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- callStateList.get(0).getNetworkType(),
- callStateList.get(0).getCallQuality()));
- } else {
- onCallAttributesChanged(new CallAttributes(
- new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()));
- }
- }
+ void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
}
/**
@@ -1773,13 +1702,14 @@
() -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state)));
}
- public void onCallStatesChanged(List<CallState> callStateList) {
+ public void onCallAttributesChanged(CallAttributes callAttributes) {
CallAttributesListener listener =
(CallAttributesListener) mTelephonyCallbackWeakRef.get();
if (listener == null) return;
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> listener.onCallStatesChanged(callStateList)));
+ () -> mExecutor.execute(() -> listener.onCallAttributesChanged(
+ callAttributes)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a1538de..a3696e3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -32,13 +32,13 @@
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
import android.telephony.emergency.EmergencyNumber;
-import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.util.ArraySet;
import android.util.Log;
@@ -741,20 +741,17 @@
* @param slotIndex for which precise call state changed. Can be derived from subId except when
* subId is invalid.
* @param subId for which precise call state changed.
- * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
- * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for
- * ringing, foreground & background calls.
- * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
- * background calls.
- * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+ * @param ringCallPreciseState ringCall state.
+ * @param foregroundCallPreciseState foreground call state.
+ * @param backgroundCallPreciseState background call state.
*/
public void notifyPreciseCallState(int slotIndex, int subId,
- @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
- @Annotation.ImsCallServiceType int[] imsServiceTypes,
- @Annotation.ImsCallType int[] imsCallTypes) {
+ @PreciseCallStates int ringCallPreciseState,
+ @PreciseCallStates int foregroundCallPreciseState,
+ @PreciseCallStates int backgroundCallPreciseState) {
try {
- sRegistry.notifyPreciseCallState(slotIndex, subId, callStates,
- imsCallIds, imsServiceTypes, imsCallTypes);
+ sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState,
+ foregroundCallPreciseState, backgroundCallPreciseState);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 517d982..959295b 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.FontScaleConverter;
import android.os.SystemProperties;
/**
@@ -273,6 +274,15 @@
* increments at runtime based on a user preference for the font size.
*/
public float scaledDensity;
+
+ /**
+ * If non-null, this will be used to calculate font sizes instead of {@link #scaledDensity}.
+ *
+ * @hide
+ */
+ @Nullable
+ public FontScaleConverter fontScaleConverter;
+
/**
* The exact physical pixels per inch of the screen in the X dimension.
*/
@@ -350,6 +360,7 @@
noncompatScaledDensity = o.noncompatScaledDensity;
noncompatXdpi = o.noncompatXdpi;
noncompatYdpi = o.noncompatYdpi;
+ fontScaleConverter = o.fontScaleConverter;
}
public void setToDefaults() {
@@ -367,6 +378,7 @@
noncompatScaledDensity = scaledDensity;
noncompatXdpi = xdpi;
noncompatYdpi = ydpi;
+ fontScaleConverter = null;
}
@Override
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4afd268..608cbda 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -122,6 +122,13 @@
public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD = "settings_new_keyboard_trackpad";
/**
+ * Enable trackpad gesture settings UI
+ * @hide
+ */
+ public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE =
+ "settings_new_keyboard_trackpad_gesture";
+
+ /**
* Enable the new pages which is implemented with SPA.
* @hide
*/
@@ -171,6 +178,7 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
@@ -190,6 +198,7 @@
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
+ PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
}
/**
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 44318bb..7e054fc 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -408,7 +408,14 @@
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
- return value * metrics.scaledDensity;
+ if (metrics.fontScaleConverter != null) {
+ return applyDimension(
+ COMPLEX_UNIT_DIP,
+ metrics.fontScaleConverter.convertSpToDp(value),
+ metrics);
+ } else {
+ return value * metrics.scaledDensity;
+ }
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ef18458..57b2d39 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -728,8 +728,8 @@
private void releaseSurfaces(boolean releaseSurfacePackage) {
mAlpha = 1f;
+ mSurface.destroy();
synchronized (mSurfaceControlLock) {
- mSurface.destroy();
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 85f5056..44b6deb 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -20,13 +20,18 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ResolveInfo;
import android.graphics.Region;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
@@ -34,14 +39,15 @@
import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to
* interact with the windows in the display that this proxy represents. Proxying the default display
- * or a display that is not tracked will throw an exception. Only the real user has access to global
- * clients like SystemUI.
+ * or a display that is not tracked by accessibility, such as private displays, will throw an
+ * exception. Only the real user has access to global clients like SystemUI.
*
* <p>
* To register and unregister a proxy, use
@@ -49,7 +55,16 @@
* and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app
* that has registered the proxy dies, the system will remove the proxy.
*
- * TODO(241429275): Complete proxy impl and add additional support (if necessary) like cache methods
+ * <p>
+ * Avoid using the app's main thread. Proxy methods such as {@link #getWindows} and node methods
+ * like {@link AccessibilityNodeInfo#getChild(int)} will happen frequently. Node methods may also
+ * wait on the displayed app's UI thread to obtain accurate screen data.
+ *
+ * <p>
+ * To get a list of {@link AccessibilityServiceInfo}s that have populated {@link ComponentName}s and
+ * {@link ResolveInfo}s, retrieve the list using {@link #getInstalledAndEnabledServices()} after
+ * {@link #onProxyConnected()} has been called.
+ *
* @hide
*/
@SystemApi
@@ -91,7 +106,134 @@
}
/**
- * An IAccessibilityServiceClient that handles interrupts and accessibility events.
+ * Handles {@link android.view.accessibility.AccessibilityEvent}s.
+ * <p>
+ * AccessibilityEvents represent changes to the UI, or what parts of the node tree have changed.
+ * AccessibilityDisplayProxy should use these to query new UI and send appropriate feedback
+ * to their users.
+ * <p>
+ * For example, a {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} indicates a change in windows,
+ * so a proxy may query {@link #getWindows} to obtain updated UI and potentially inform of a new
+ * window title. Or a proxy may emit an earcon on a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+ */
+ public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+ // Default no-op
+ }
+
+ /**
+ * Handles a successful system connection after
+ * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} is called.
+ *
+ * <p>
+ * At this point, querying for UI is available and {@link AccessibilityEvent}s will begin being
+ * sent. An AccessibilityDisplayProxy may instantiate core infrastructure components here.
+ */
+ public void onProxyConnected() {
+ // Default no-op
+ }
+
+ /**
+ * Handles a request to interrupt the accessibility feedback.
+ * <p>
+ * AccessibilityDisplayProxy should interrupt the accessibility activity occurring on its
+ * display. For example, a screen reader may interrupt speech.
+ *
+ * @see AccessibilityManager#interrupt()
+ * @see AccessibilityService#onInterrupt()
+ */
+ public void interrupt() {
+ // Default no-op
+ }
+
+ /**
+ * Gets the focus of the window specified by {@code windowInfo}.
+ *
+ * @param windowInfo the window to search
+ * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ * @hide
+ * TODO(254545943): Do not expose until support for accessibility focus and/or input is in place
+ */
+ @Nullable
+ public AccessibilityNodeInfo findFocus(@NonNull AccessibilityWindowInfo windowInfo, int focus) {
+ AccessibilityNodeInfo windowRoot = windowInfo.getRoot();
+ return windowRoot != null ? windowRoot.findFocus(focus) : null;
+ }
+
+ /**
+ * Gets the windows of the tracked display.
+ *
+ * @see AccessibilityService#getWindows()
+ */
+ @NonNull
+ public List<AccessibilityWindowInfo> getWindows() {
+ return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(mConnectionId,
+ mDisplayId);
+ }
+
+ /**
+ * Sets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+ * {@link AccessibilityDisplayProxy}'s display.
+ *
+ * <p>These represent a11y features and services that are installed and running. These should
+ * not include {@link AccessibilityService}s installed on the phone.
+ *
+ * @param installedAndEnabledServices the list of installed and running a11y services.
+ */
+ public void setInstalledAndEnabledServices(
+ @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) {
+ mInstalledAndEnabledServices = installedAndEnabledServices;
+ sendServiceInfos();
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+ * properly set and there is an {@link IAccessibilityServiceConnection} to the
+ * AccessibilityManagerService.
+ */
+ private void sendServiceInfos() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (mInstalledAndEnabledServices != null && mInstalledAndEnabledServices.size() > 0
+ && connection != null) {
+ try {
+ connection.setInstalledAndEnabledServices(mInstalledAndEnabledServices);
+ AccessibilityInteractionClient.getInstance().clearCache(mConnectionId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfos", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ mInstalledAndEnabledServices = null;
+ }
+
+ /**
+ * Gets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+ * {@link AccessibilityDisplayProxy}'s display.
+ *
+ * @return The {@link AccessibilityServiceInfo}s of interested services.
+ * @see AccessibilityServiceInfo
+ */
+ @NonNull
+ public final List<AccessibilityServiceInfo> getInstalledAndEnabledServices() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getInstalledAndEnabledServices();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * An IAccessibilityServiceClient that handles interrupts, accessibility events, and system
+ * connection.
*/
private class IAccessibilityServiceClientImpl extends
AccessibilityService.IAccessibilityServiceClientWrapper {
@@ -100,17 +242,24 @@
super(context, executor, new AccessibilityService.Callbacks() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
- // TODO: call AccessiiblityProxy.onAccessibilityEvent
+ // TODO(254545943): Remove check when event processing is done more upstream in
+ // AccessibilityManagerService.
+ if (event.getDisplayId() == mDisplayId) {
+ AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
+ }
}
@Override
public void onInterrupt() {
- // TODO: call AccessiiblityProxy.onInterrupt
+ AccessibilityDisplayProxy.this.interrupt();
}
+
@Override
public void onServiceConnected() {
- // TODO: send service infos and call AccessiiblityProxy.onProxyConnected
+ AccessibilityDisplayProxy.this.sendServiceInfos();
+ AccessibilityDisplayProxy.this.onProxyConnected();
}
+
@Override
public void init(int connectionId, IBinder windowToken) {
mConnectionId = connectionId;
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 7030ab5..06a6de9 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -458,15 +458,21 @@
* @return The {@link AccessibilityWindowInfo} list.
*/
public List<AccessibilityWindowInfo> getWindows(int connectionId) {
- final SparseArray<List<AccessibilityWindowInfo>> windows =
- getWindowsOnAllDisplays(connectionId);
- if (windows.size() > 0) {
- return windows.valueAt(Display.DEFAULT_DISPLAY);
- }
- return Collections.emptyList();
+ return getWindowsOnDisplay(connectionId, Display.DEFAULT_DISPLAY);
}
/**
+ * Gets the info for all windows of the specified display.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @return The {@link AccessibilityWindowInfo} list belonging to {@code displayId}.
+ */
+ public List<AccessibilityWindowInfo> getWindowsOnDisplay(int connectionId, int displayId) {
+ final SparseArray<List<AccessibilityWindowInfo>> windows =
+ getWindowsOnAllDisplays(connectionId);
+ return windows.get(displayId, Collections.emptyList());
+ }
+ /**
* Gets the info for all windows of all displays.
*
* @param connectionId The id of a connection for interacting with the system.
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index 76e068d..6ceccd1 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -17,14 +17,20 @@
package com.android.internal.content.om;
import static android.content.Context.MODE_PRIVATE;
+import static android.content.om.OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY;
import android.annotation.NonNull;
+import android.annotation.NonUiContext;
import android.content.Context;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
@@ -48,6 +54,7 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -129,10 +136,9 @@
}
}
- /**
- * Ensure the base dir for self-targeting is valid.
- */
+ /** Ensure the base dir for self-targeting is valid. */
@VisibleForTesting
+ @NonUiContext
public void ensureBaseDir() {
final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath();
final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName();
@@ -170,6 +176,16 @@
mBasePath = baseFile.toPath();
}
+ private boolean isSameWithTargetSignature(final String targetPackage) {
+ final PackageManager packageManager = mContext.getPackageManager();
+ final String packageName = mContext.getPackageName();
+ if (TextUtils.equals(packageName, targetPackage)) {
+ return true;
+ }
+ return packageManager.checkSignatures(packageName, targetPackage)
+ == PackageManager.SIGNATURE_MATCH;
+ }
+
/**
* Check if the overlay name is valid or not.
*
@@ -202,8 +218,12 @@
/**
* Save FabricatedOverlay instance as frro and idmap files.
*
+ * <p>In order to fill the overlayable policy, it's necessary to collect the information from
+ * app. And then, the information is passed to native layer to fill the overlayable policy
+ *
* @param overlayInternal the FabricatedOverlayInternal to be saved.
*/
+ @NonUiContext
public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal)
throws IOException, PackageManager.NameNotFoundException {
ensureBaseDir();
@@ -214,6 +234,9 @@
final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
checkPackageName(overlayInternal.packageName);
checkPackageName(overlayInternal.targetPackageName);
+ Preconditions.checkStringNotEmpty(
+ overlayInternal.targetOverlayable,
+ "Target overlayable should be neither null nor empty string.");
final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
final String targetPackage = Preconditions.checkStringNotEmpty(
@@ -223,7 +246,17 @@
createFrroFile(frroPath.toString(), overlayInternal);
try {
- createIdmapFile(targetPackage, frroPath.toString(), idmapPath.toString(), overlayName);
+ createIdmapFile(
+ targetPackage,
+ frroPath.toString(),
+ idmapPath.toString(),
+ overlayName,
+ applicationInfo.isSystemApp() || applicationInfo.isSystemExt() /* isSystem */,
+ applicationInfo.isVendor(),
+ applicationInfo.isProduct(),
+ isSameWithTargetSignature(overlayInternal.targetPackageName),
+ applicationInfo.isOdm(),
+ applicationInfo.isOem());
} catch (IOException e) {
if (!frroPath.toFile().delete()) {
Log.w(TAG, "Failed to delete file " + frroPath);
@@ -237,6 +270,7 @@
*
* @param overlayName the specific name
*/
+ @NonUiContext
public void unregisterFabricatedOverlay(@NonNull String overlayName) {
ensureBaseDir();
checkOverlayNameValid(overlayName);
@@ -252,6 +286,46 @@
}
/**
+ * Commit the overlay manager transaction
+ *
+ * @param transaction the overlay manager transaction
+ */
+ @NonUiContext
+ public void commit(@NonNull OverlayManagerTransaction transaction)
+ throws PackageManager.NameNotFoundException, IOException {
+ Objects.requireNonNull(transaction);
+
+ for (Iterator<OverlayManagerTransaction.Request> it = transaction.iterator();
+ it.hasNext(); ) {
+ final OverlayManagerTransaction.Request request = it.next();
+ if (request.type == TYPE_REGISTER_FABRICATED) {
+ final FabricatedOverlayInternal fabricatedOverlayInternal =
+ Objects.requireNonNull(
+ request.extras.getParcelable(
+ BUNDLE_FABRICATED_OVERLAY,
+ FabricatedOverlayInternal.class));
+
+ // populate the mandatory data
+ if (TextUtils.isEmpty(fabricatedOverlayInternal.packageName)) {
+ fabricatedOverlayInternal.packageName = mContext.getPackageName();
+ } else {
+ if (!TextUtils.equals(
+ fabricatedOverlayInternal.packageName, mContext.getPackageName())) {
+ throw new IllegalArgumentException("Unknown package name in transaction");
+ }
+ }
+
+ registerFabricatedOverlay(fabricatedOverlayInternal);
+ } else if (request.type == TYPE_UNREGISTER_FABRICATED) {
+ final OverlayIdentifier overlayIdentifier = Objects.requireNonNull(request.overlay);
+ unregisterFabricatedOverlay(overlayIdentifier.getOverlayName());
+ } else {
+ throw new IllegalArgumentException("Unknown request in transaction " + request);
+ }
+ }
+ }
+
+ /**
* Get the list of overlays information for the target package name.
*
* @param targetPackage the target package name
@@ -315,7 +389,13 @@
@NonNull String targetPath,
@NonNull String overlayPath,
@NonNull String idmapPath,
- @NonNull String overlayName)
+ @NonNull String overlayName,
+ boolean isSystem,
+ boolean isVendor,
+ boolean isProduct,
+ boolean isSameWithTargetSignature,
+ boolean isOdm,
+ boolean isOem)
throws IOException;
private static native FabricatedOverlayInfo getFabricatedOverlayInfo(
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 9cb2e68..4b1753a 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -17,7 +17,7 @@
package com.android.internal.telephony;
import android.telephony.BarringInfo;
-import android.telephony.CallState;
+import android.telephony.CallAttributes;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
@@ -62,7 +62,7 @@
void onPhoneCapabilityChanged(in PhoneCapability capability);
void onActiveDataSubIdChanged(in int subId);
void onRadioPowerStateChanged(in int state);
- void onCallStatesChanged(in List<CallState> callStateList);
+ void onCallAttributesChanged(in CallAttributes callAttributes);
@SuppressWarnings(value={"untyped-collection"})
void onEmergencyNumberListChanged(in Map emergencyNumberList);
void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 7ba2686..c7fa757 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -66,8 +66,8 @@
void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
- void notifyPreciseCallState(int phoneId, int subId, in int[] callStates, in String[] imsCallIds,
- in int[] imsCallServiceTypes, in int[] imsCallTypes);
+ void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
+ int foregroundCallState, int backgroundCallState);
void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
int preciseDisconnectCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index 1eb446e..f974d9d 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -174,6 +174,9 @@
} else {
dump.write("supported_modes", UsbPortProto.SUPPORTED_MODES, UsbPort.modeToString(mode));
}
+ dump.write("supports_compliance_warnings",
+ UsbPortProto.SUPPORTS_COMPLIANCE_WARNINGS,
+ port.supportsComplianceWarnings());
dump.end(token);
}
@@ -250,6 +253,8 @@
status.isPowerTransferLimited());
dump.write("usb_power_brick_status", UsbPortStatusProto.USB_POWER_BRICK_STATUS,
UsbPort.powerBrickConnectionStatusToString(status.getPowerBrickConnectionStatus()));
+ dump.write("compliance_warning_status", UsbPortStatusProto.COMPLIANCE_WARNINGS_STRING,
+ UsbPort.complianceWarningsToString(status.getComplianceWarnings()));
dump.end(token);
}
}
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index df55e42..bba1760 100644
--- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -123,8 +123,12 @@
bool callCreateIdmapFile(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath, const std::string& idmapPath,
- const std::string& overlayName) {
- return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName);
+ const std::string& overlayName, const bool isSystem,
+ const bool isVendor, const bool isProduct,
+ const bool isTargetSignature, const bool isOdm, const bool isOem) {
+ return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName,
+ isSystem, isVendor, isProduct, isTargetSignature, isOdm,
+ isOem);
}
bool callGetFabricatedOverlayInfo(std::string& out_error, const std::string& overlay_path,
@@ -158,7 +162,10 @@
typedef bool (*CreateIdmapFileFunc)(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath,
const std::string& idmapPath,
- const std::string& overlayName);
+ const std::string& overlayName, const jboolean isSystem,
+ const jboolean isVendor, const jboolean isProduct,
+ const jboolean isSameWithTargetSignature,
+ const jboolean isOdm, const jboolean isOem);
typedef bool (*GetFabricatedOverlayInfoFunc)(std::string& out_error,
const std::string& overlay_path,
@@ -295,7 +302,9 @@
}
static void CreateIdmapFile(JNIEnv* env, jclass /* clazz */, jstring jsTargetPath,
- jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName) {
+ jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName,
+ jboolean isSystem, jboolean isVendor, jboolean isProduct,
+ jboolean isTargetSignature, jboolean isOdm, jboolean isOem) {
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
jniThrowNullPointerException(env, "libidmap2 is not loaded");
@@ -327,7 +336,10 @@
std::string err_result;
if (!dlLoader.callCreateIdmapFile(err_result, targetPath.c_str(), overlayPath.c_str(),
- idmapPath.c_str(), overlayName.c_str())) {
+ idmapPath.c_str(), overlayName.c_str(),
+ (isSystem == JNI_TRUE), (isVendor == JNI_TRUE),
+ (isProduct == JNI_TRUE), (isTargetSignature == JNI_TRUE),
+ (isOdm == JNI_TRUE), (isOem == JNI_TRUE))) {
jniThrowException(env, kIOException, err_result.c_str());
return;
}
@@ -374,7 +386,7 @@
{"createFrroFile", "(Ljava/lang/String;Landroid/os/FabricatedOverlayInternal;)V",
reinterpret_cast<void*>(self_targeting::CreateFrroFile)},
{"createIdmapFile",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZZZ)V",
reinterpret_cast<void*>(self_targeting::CreateIdmapFile)},
{"getFabricatedOverlayInfo", "(Ljava/lang/String;)Landroid/os/FabricatedOverlayInfo;",
reinterpret_cast<void*>(self_targeting::GetFabricatedOverlayInfo)},
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index df5e0a9..607fd10 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -240,6 +240,7 @@
// ID of the port. A device (eg: Chromebooks) might have multiple ports.
optional string id = 1;
repeated Mode supported_modes = 2;
+ optional bool supports_compliance_warnings = 3;
}
message UsbPortStatusProto {
@@ -268,6 +269,7 @@
optional string usb_data_status = 7;
optional bool is_power_transfer_limited = 8;
optional string usb_power_brick_status = 9;
+ optional string compliance_warnings_string = 10;
}
message UsbPortStatusRoleCombinationProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5a7abcc..ecc3979 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -293,6 +293,7 @@
<protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
<protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" />
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index 6a01abe..f1ab696 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -17,7 +17,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.gamemanagertests"
- android:sharedUserId="android.uid.system" >
+ android:sharedUserId="com.android.uid.test" >
+
+ <uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
<application android:appCategory="game">
<uses-library android:name="android.test.runner" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 48cfc87..e811bb6 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -43,10 +43,12 @@
"mockwebserver",
"guava",
"androidx.core_core",
+ "androidx.core_core-ktx",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
"androidx.test.runner",
"androidx.test.rules",
+ "junit-params",
"kotlin-test",
"mockito-target-minus-junit4",
"ub-uiautomator",
diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
index a648a88..fc69f69 100644
--- a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
+++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
@@ -17,19 +17,26 @@
package android.app.time;
import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_NOT_APPLICABLE;
import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
import android.service.timezone.TimeZoneProviderStatus;
@@ -207,4 +214,114 @@
assertEquals(status,
LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
}
+
+ @Test
+ public void testCouldEnableTelephonyFallback_notRunning() {
+ LocationTimeZoneAlgorithmStatus notRunning =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+ assertFalse(notRunning.couldEnableTelephonyFallback());
+ }
+
+ @Test
+ public void testCouldEnableTelephonyFallback_unknown() {
+ // DETECTION_ALGORITHM_STATUS_UNKNOWN must never allow fallback
+ LocationTimeZoneAlgorithmStatus unknown =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+ assertFalse(unknown.couldEnableTelephonyFallback());
+ }
+
+ @Test
+ public void testCouldEnableTelephonyFallback_notSupported() {
+ // DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED must never allow fallback
+ LocationTimeZoneAlgorithmStatus notSupported =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+ assertFalse(notSupported.couldEnableTelephonyFallback());
+ }
+
+ @Test
+ public void testCouldEnableTelephonyFallback_running() {
+ // DETECTION_ALGORITHM_STATUS_RUNNING may allow fallback
+
+ // Sample provider-reported statuses that do / do not enable fallback.
+ TimeZoneProviderStatus enableTelephonyFallbackProviderStatus =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionDependencyStatus(
+ DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE)
+ .build();
+ assertTrue(enableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback());
+
+ TimeZoneProviderStatus notEnableTelephonyFallbackProviderStatus =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE)
+ .build();
+ assertFalse(notEnableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback());
+
+ // Provider not ready: Never enable fallback
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+ assertFalse(status.couldEnableTelephonyFallback());
+ }
+
+ // Provider uncertain without reported status: Never enable fallback
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_READY, null);
+ assertFalse(status.couldEnableTelephonyFallback());
+ }
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ assertFalse(status.couldEnableTelephonyFallback());
+ }
+
+ // Provider uncertain with reported status: Fallback is based on the status for present
+ // providers that report their status. All present providers must have reported status and
+ // agree that fallback is a good idea.
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+ PROVIDER_STATUS_NOT_READY, null);
+ assertFalse(status.couldEnableTelephonyFallback());
+ }
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertTrue(status.couldEnableTelephonyFallback());
+ }
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+ PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus);
+ assertTrue(status.couldEnableTelephonyFallback());
+ }
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+ PROVIDER_STATUS_IS_UNCERTAIN, notEnableTelephonyFallbackProviderStatus);
+ assertFalse(status.couldEnableTelephonyFallback());
+ }
+ {
+ LocationTimeZoneAlgorithmStatus status =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus);
+ assertTrue(status.couldEnableTelephonyFallback());
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
new file mode 100644
index 0000000..cfca037
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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 android.content.res
+
+import androidx.core.util.forEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterFactoryTest {
+
+ @Test
+ fun scale200IsTwiceAtSmallSizes() {
+ val table = FontScaleConverterFactory.forScale(2F)!!
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @SmallTest
+ fun missingLookupTableReturnsNull() {
+ assertThat(FontScaleConverterFactory.forScale(3F)).isNull()
+ }
+
+ @SmallTest
+ fun missingLookupTable105ReturnsNull() {
+ assertThat(FontScaleConverterFactory.forScale(1.05F)).isNull()
+ }
+
+ @SmallTest
+ fun missingLookupTableNegativeReturnsNull() {
+ assertThat(FontScaleConverterFactory.forScale(-1F)).isNull()
+ }
+
+ @SmallTest
+ fun unnecessaryFontScalesReturnsNull() {
+ assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
+ assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
+ assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
+ }
+
+ @SmallTest
+ fun tablesMatchAndAreMonotonicallyIncreasing() {
+ FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable ->
+ assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size)
+ assertThat(lookupTable.mToDpValues).isNotEmpty()
+
+ assertThat(lookupTable.mFromSpValues.asList()).isInStrictOrder()
+ assertThat(lookupTable.mToDpValues.asList()).isInStrictOrder()
+
+ assertThat(lookupTable.mFromSpValues.asList()).containsNoDuplicates()
+ assertThat(lookupTable.mToDpValues.asList()).containsNoDuplicates()
+ }
+ }
+
+ companion object {
+ private const val CONVERSION_TOLERANCE = 0.05f
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
new file mode 100644
index 0000000..e405c55
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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 android.content.res
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterTest {
+
+ @Test
+ fun straightInterpolation() {
+ val table = createTable(8f to 8f, 10f to 10f, 20f to 20f)
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+ assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+ assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @Test
+ fun interpolate200Percent() {
+ val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+ assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(60f)
+ assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(40f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @Test
+ fun interpolate150Percent() {
+ val table = createTable(2f to 3f, 10f to 15f, 20f to 30f, 100f to 150f)
+ assertThat(table.convertSpToDp(2F)).isWithin(CONVERSION_TOLERANCE).of(3f)
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.5f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(12f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(15f)
+ assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+ assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(75f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(7.5f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @Test
+ fun pastEndsUsesLastScalingFactor() {
+ val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+ assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(200f)
+ assertThat(table.convertSpToDp(31F)).isWithin(CONVERSION_TOLERANCE).of(62f)
+ assertThat(table.convertSpToDp(1000F)).isWithin(CONVERSION_TOLERANCE).of(2000f)
+ assertThat(table.convertSpToDp(2000F)).isWithin(CONVERSION_TOLERANCE).of(4000f)
+ assertThat(table.convertSpToDp(10000F)).isWithin(CONVERSION_TOLERANCE).of(20000f)
+ }
+
+ @Test
+ fun negativeSpIsNegativeDp() {
+ val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+ assertThat(table.convertSpToDp(-1F)).isWithin(CONVERSION_TOLERANCE).of(-2f)
+ assertThat(table.convertSpToDp(-8F)).isWithin(CONVERSION_TOLERANCE).of(-16f)
+ assertThat(table.convertSpToDp(-10F)).isWithin(CONVERSION_TOLERANCE).of(-20f)
+ assertThat(table.convertSpToDp(-30F)).isWithin(CONVERSION_TOLERANCE).of(-60f)
+ assertThat(table.convertSpToDp(-20F)).isWithin(CONVERSION_TOLERANCE).of(-40f)
+ assertThat(table.convertSpToDp(-5F)).isWithin(CONVERSION_TOLERANCE).of(-10f)
+ assertThat(table.convertSpToDp(-0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ private fun createTable(vararg pairs: Pair<Float, Float>) =
+ FontScaleConverter(
+ pairs.map { it.first }.toFloatArray(),
+ pairs.map { it.second }.toFloatArray()
+ )
+
+ companion object {
+ private const val CONVERSION_TOLERANCE = 0.05f
+ }
+}
diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java
index c0325ca..8e63a0f 100644
--- a/core/tests/coretests/src/android/os/EnvironmentTest.java
+++ b/core/tests/coretests/src/android/os/EnvironmentTest.java
@@ -47,29 +47,6 @@
return InstrumentationRegistry.getContext();
}
- /**
- * Sets {@code mode} for the given {@code ops} and the given {@code uid}.
- *
- * <p>This method drops shell permission identity.
- */
- private static void setAppOpsModeForUid(int uid, int mode, String... ops) {
- if (ops == null) {
- return;
- }
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity();
- try {
- for (String op : ops) {
- getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
- }
- } finally {
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .dropShellPermissionIdentity();
- }
- }
-
@Before
public void setUp() throws Exception {
dir = getContext().getDir("testing", Context.MODE_PRIVATE);
@@ -127,17 +104,4 @@
Environment.buildPath(dir, "Taxes.pdf").createNewFile();
assertEquals(HAS_OTHER, classifyExternalStorageDirectory(dir));
}
-
- @Test
- public void testIsExternalStorageManager() throws Exception {
- assertFalse(Environment.isExternalStorageManager());
- try {
- setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED,
- AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
- assertTrue(Environment.isExternalStorageManager());
- } finally {
- setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_DEFAULT,
- AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
- }
- }
}
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index 9006cd9..0c1630e 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,15 +16,31 @@
package android.service.timezone;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
import static org.junit.Assert.assertEquals;
+import android.service.timezone.TimeZoneProviderStatus.DependencyStatus;
+import android.service.timezone.TimeZoneProviderStatus.OperationStatus;
+
import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
/** Non-SDK tests. See CTS for SDK API tests. */
+@RunWith(JUnitParamsRunner.class)
public class TimeZoneProviderStatusTest {
@Test
@@ -37,4 +53,51 @@
assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
}
+
+ @Test
+ @Parameters(method = "couldEnableTelephonyFallbackParams")
+ public void couldEnableTelephonyFallback(@DependencyStatus int locationDetectionStatus,
+ @DependencyStatus int connectivityStatus, @OperationStatus int tzResolutionStatus) {
+ TimeZoneProviderStatus providerStatus =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionDependencyStatus(locationDetectionStatus)
+ .setConnectivityDependencyStatus(connectivityStatus)
+ .setTimeZoneResolutionOperationStatus(tzResolutionStatus)
+ .build();
+ boolean locationDetectionStatusCouldEnableFallback =
+ (locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+ || locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+ boolean connectivityStatusCouldEnableFallback =
+ (connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+ || connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+ boolean tzResolutionStatusCouldEnableFallback = false;
+
+ assertEquals(locationDetectionStatusCouldEnableFallback
+ || connectivityStatusCouldEnableFallback
+ || tzResolutionStatusCouldEnableFallback,
+ providerStatus.couldEnableTelephonyFallback());
+ }
+
+ public static Integer[][] couldEnableTelephonyFallbackParams() {
+ List<Integer[]> params = new ArrayList<>();
+ @DependencyStatus int[] dependencyStatuses =
+ IntStream.rangeClosed(
+ DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS).toArray();
+ @OperationStatus int[] operationStatuses =
+ IntStream.rangeClosed(OPERATION_STATUS_UNKNOWN, OPERATION_STATUS_FAILED).toArray();
+
+ // Cartesian product: dependencyStatus x dependencyStatus x operationStatus
+ for (@DependencyStatus int locationDetectionStatus : dependencyStatuses) {
+ for (@DependencyStatus int connectivityStatus : dependencyStatuses) {
+ for (@OperationStatus int tzResolutionStatus : operationStatuses) {
+ params.add(new Integer[] {
+ locationDetectionStatus,
+ connectivityStatus,
+ tzResolutionStatus
+ });
+ }
+ }
+ }
+ return params.toArray(new Integer[0][0]);
+ }
}
diff --git a/core/tests/coretests/src/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt
index 7a05d97..7d98a7d 100644
--- a/core/tests/coretests/src/android/util/TypedValueTest.kt
+++ b/core/tests/coretests/src/android/util/TypedValueTest.kt
@@ -16,16 +16,17 @@
package android.util
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.abs
+import kotlin.math.min
+import kotlin.math.roundToInt
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
-import kotlin.math.abs
-import kotlin.math.min
-import kotlin.math.roundToInt
@RunWith(AndroidJUnit4::class)
class TypedValueTest {
@@ -152,4 +153,19 @@
val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics)
assertEquals(widthFloat.roundToInt(), widthPx)
}
-}
\ No newline at end of file
+
+ @SmallTest
+ @Test
+ fun testNonLinearFontScaling_nullLookupFallsBackToScaledDensity() {
+ val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
+ val fontScale = 2f
+ metrics.density = 1f
+ metrics.scaledDensity = fontScale * metrics.density
+ metrics.fontScaleConverter = null
+
+ assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, metrics))
+ .isEqualTo(20f)
+ assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50f, metrics))
+ .isEqualTo(100f)
+ }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index ee1e10f..7cbf3ffa 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -263,10 +263,24 @@
}
private class MyAccessibilityProxy extends AccessibilityDisplayProxy {
- // TODO(241429275): Will override A11yProxy methods in the future.
MyAccessibilityProxy(int displayId,
@NonNull List<AccessibilityServiceInfo> serviceInfos) {
super(displayId, Executors.newSingleThreadExecutor(), serviceInfos);
}
+
+ @Override
+ public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+
+ }
+
+ @Override
+ public void onProxyConnected() {
+
+ }
+
+ @Override
+ public void interrupt() {
+
+ }
}
}
diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp
index 82998db..063c569 100644
--- a/core/tests/overlaytests/device_self_targeting/Android.bp
+++ b/core/tests/overlaytests/device_self_targeting/Android.bp
@@ -29,6 +29,7 @@
"androidx.test.rules",
"androidx.test.runner",
"androidx.test.ext.junit",
+ "mockito-target-minus-junit4",
"truth-prebuilt",
],
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
new file mode 100644
index 0000000..5cc214d
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
@@ -0,0 +1,84 @@
+<?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.
+ -->
+
+<resources>
+ <overlayable name="PublicOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="public">
+ <item type="color" name="public_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="SignatureOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="signature">
+ <item type="color" name="mycolor" />
+ <item type="color" name="signature_overlayable_color" />
+ <item type="string" name="mystring" />
+ <item type="drawable" name="mydrawable" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="SystemAppOverlayable" actor="overlay://theme">
+ <!-- The app in system partition can overlay the below resources -->
+ <policy type="system">
+ <item type="color" name="system_app_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="OdmOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="odm">
+ <item type="color" name="odm_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="OemOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="oem">
+ <item type="color" name="oem_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="VendorOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="vendor">
+ <item type="color" name="vendor_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="ProductOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="product">
+ <item type="color" name="product_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="ActorOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="actor">
+ <item type="color" name="actor_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="ConfigOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="config_signature">
+ <item type="color" name="config_overlayable_color" />
+ </policy>
+ </overlayable>
+
+</resources>
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/values.xml b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
index f0b4a6f..d82de97 100644
--- a/core/tests/overlaytests/device_self_targeting/res/values/values.xml
+++ b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
@@ -17,4 +17,14 @@
<resources>
<color name="mycolor">#ff112233</color>
<string name="mystring">hello</string>
+
+ <color name="public_overlayable_color">#000</color>
+ <color name="signature_overlayable_color">#000</color>
+ <color name="system_app_overlayable_color">#000</color>
+ <color name="odm_overlayable_color">#000</color>
+ <color name="oem_overlayable_color">#000</color>
+ <color name="vendor_overlayable_color">#000</color>
+ <color name="product_overlayable_color">#000</color>
+ <color name="actor_overlayable_color">#000</color>
+ <color name="config_overlayable_color">#000</color>
</resources>
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index ca58410..40d0bef 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -17,15 +17,24 @@
package com.android.overlaytest;
import static android.content.Context.MODE_PRIVATE;
+import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
import static com.android.internal.content.om.OverlayManagerImpl.SELF_TARGET;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.om.FabricatedOverlay;
import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.FabricatedOverlayInternal;
@@ -72,11 +81,23 @@
private static final String TARGET_COLOR_RES = "color/mycolor";
private static final String TARGET_STRING_RES = "string/mystring";
private static final String TARGET_DRAWABLE_RES = "drawable/mydrawable";
+ private static final String PUBLIC_OVERLAYABLE = "PublicOverlayable";
+ private static final String SIGNATURE_OVERLAYABLE = "SignatureOverlayable";
+ private static final String SYSTEM_APP_OVERLAYABLE = "SystemAppOverlayable";
+ private static final String ODM_OVERLAYABLE = "OdmOverlayable";
+ private static final String OEM_OVERLAYABLE = "OemOverlayable";
+ private static final String VENDOR_OVERLAYABLE = "VendorOverlayable";
+ private static final String PRODUCT_OVERLAYABLE = "ProductOverlayable";
+ private static final String ACTOR_OVERLAYABLE = "ActorOverlayable";
+ private static final String CONFIG_OVERLAYABLE = "ConfigOverlayable";
private Context mContext;
private OverlayManagerImpl mOverlayManagerImpl;
private String mOverlayName;
+ private PackageManager mMockPackageManager;
+ private ApplicationInfo mMockApplicationInfo;
+
@Rule public TestName mTestName = new TestName();
@Rule public Expect expect = Expect.create();
@@ -111,7 +132,36 @@
public void setUp() throws IOException {
clearDir();
mOverlayName = mTestName.getMethodName();
- mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mMockApplicationInfo = mock(ApplicationInfo.class);
+ when(mMockApplicationInfo.isSystemApp()).thenReturn(false);
+ when(mMockApplicationInfo.isSystemExt()).thenReturn(false);
+ when(mMockApplicationInfo.isOdm()).thenReturn(false);
+ when(mMockApplicationInfo.isOem()).thenReturn(false);
+ when(mMockApplicationInfo.isVendor()).thenReturn(false);
+ when(mMockApplicationInfo.isProduct()).thenReturn(false);
+ when(mMockApplicationInfo.getBaseCodePath()).thenReturn(
+ context.getApplicationInfo().getBaseCodePath());
+ mMockApplicationInfo.sourceDir = context.getApplicationInfo().sourceDir;
+
+ mMockPackageManager = mock(PackageManager.class);
+ when(mMockPackageManager.checkSignatures(anyString(), anyString()))
+ .thenReturn(SIGNATURE_NO_MATCH);
+
+ mContext =
+ new ContextWrapper(context) {
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return mMockApplicationInfo;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
+ };
+
mOverlayManagerImpl = new OverlayManagerImpl(mContext);
}
@@ -144,12 +194,14 @@
private <T> FabricatedOverlayInternal createOverlayWithName(
@NonNull String overlayName,
+ @NonNull String targetOverlayable,
@NonNull String targetPackageName,
@NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) {
final String packageName = mContext.getPackageName();
FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal();
overlayInternal.overlayName = overlayName;
overlayInternal.targetPackageName = targetPackageName;
+ overlayInternal.targetOverlayable = targetOverlayable;
overlayInternal.packageName = packageName;
addOverlayEntry(overlayInternal, entryDefinitions);
@@ -162,6 +214,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SYSTEM_APP_OVERLAYABLE,
"android",
List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
@@ -190,6 +243,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
@@ -215,6 +269,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_STRING_RES, Pair.create(null, "HELLO"))));
@@ -242,6 +297,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_DRAWABLE_RES,
Pair.create(null, parcelFileDescriptor))));
@@ -268,6 +324,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create("color/not_existed", Pair.create(null, "HELLO"))));
@@ -301,6 +358,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -309,6 +367,7 @@
overlayInternal =
createOverlayWithName(
secondOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -341,6 +400,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -349,6 +409,7 @@
overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -392,6 +453,40 @@
}
@Test
+ public void commit_withNullTransaction_shouldFail() {
+ assertThrows(NullPointerException.class, () -> mOverlayManagerImpl.commit(null));
+ }
+
+ @Test
+ public void commitRegisterOverlay_fromOtherBuilder_shouldWork()
+ throws PackageManager.NameNotFoundException, IOException {
+ FabricatedOverlay overlay =
+ new FabricatedOverlay.Builder(
+ mContext.getPackageName(), mOverlayName, mContext.getPackageName())
+ .setTargetOverlayable(SIGNATURE_OVERLAYABLE)
+ .setResourceValue(
+ TARGET_COLOR_RES, TypedValue.TYPE_INT_COLOR_ARGB8, Color.WHITE)
+ .build();
+ OverlayManagerTransaction transaction =
+ new OverlayManagerTransaction.Builder().registerFabricatedOverlay(overlay).build();
+
+ mOverlayManagerImpl.commit(transaction);
+
+ final List<OverlayInfo> overlayInfos =
+ mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+ final int firstNumberOfOverlays = overlayInfos.size();
+ expect.that(firstNumberOfOverlays).isEqualTo(1);
+ final OverlayInfo overlayInfo = overlayInfos.get(0);
+ expect.that(overlayInfo).isNotNull();
+ Truth.assertThat(expect.hasFailures()).isFalse();
+ expect.that(overlayInfo.isFabricated()).isTrue();
+ expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
+ expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
+ expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
+ expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
+ }
+
+ @Test
public void newOverlayManagerImpl_forOtherUser_shouldFail() {
Context fakeContext =
new ContextWrapper(mContext) {
@@ -408,4 +503,177 @@
assertThrows(SecurityException.class, () -> new OverlayManagerImpl(fakeContext));
}
+
+ FabricatedOverlayInternal prepareFabricatedOverlayInternal(
+ String targetOverlayableName, String targetEntryName) {
+ return createOverlayWithName(
+ mOverlayName,
+ targetOverlayableName,
+ mContext.getPackageName(),
+ List.of(
+ Pair.create(
+ targetEntryName,
+ Pair.create(null, Color.WHITE))));
+ }
+
+ @Test
+ public void registerOverlayOnSystemOverlayable_selfIsNotSystemApp_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ SYSTEM_APP_OVERLAYABLE,
+ "color/system_app_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnOdmOverlayable_selfIsNotOdm_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ ODM_OVERLAYABLE,
+ "color/odm_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnOemOverlayable_selfIsNotOem_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ OEM_OVERLAYABLE,
+ "color/oem_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnVendorOverlayable_selfIsNotVendor_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ VENDOR_OVERLAYABLE,
+ "color/vendor_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnProductOverlayable_selfIsNotProduct_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ PRODUCT_OVERLAYABLE,
+ "color/product_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnActorOverlayable_notSupport_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ ACTOR_OVERLAYABLE,
+ "color/actor_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnConfigOverlayable_notSupport_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ CONFIG_OVERLAYABLE,
+ "color/config_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnPublicOverlayable_shouldAlwaysSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ PUBLIC_OVERLAYABLE,
+ "color/public_overlayable_color");
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnSystemOverlayable_selfIsSystemApp_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ SYSTEM_APP_OVERLAYABLE,
+ "color/system_app_overlayable_color");
+ when(mMockApplicationInfo.isSystemApp()).thenReturn(true);
+ when(mMockApplicationInfo.isSystemExt()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnOdmOverlayable_selfIsOdm_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ ODM_OVERLAYABLE,
+ "color/odm_overlayable_color");
+ when(mMockApplicationInfo.isOdm()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnOemOverlayable_selfIsOem_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ OEM_OVERLAYABLE,
+ "color/oem_overlayable_color");
+ when(mMockApplicationInfo.isOem()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnVendorOverlayable_selfIsVendor_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ VENDOR_OVERLAYABLE,
+ "color/vendor_overlayable_color");
+ when(mMockApplicationInfo.isVendor()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnProductOverlayable_selfIsProduct_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ PRODUCT_OVERLAYABLE,
+ "color/product_overlayable_color");
+ when(mMockApplicationInfo.isProduct()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
}
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
new file mode 100644
index 0000000..f0a0cf4
--- /dev/null
+++ b/graphics/java/android/graphics/Mesh.java
@@ -0,0 +1,317 @@
+/*
+ * 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 android.graphics;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Class representing a mesh object.
+ *
+ * This class generates Mesh objects via the
+ * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and
+ * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
+ * where a {@link MeshSpecification} is required along with various attributes for
+ * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
+ * for the mesh.
+ *
+ * @hide
+ */
+public class Mesh {
+ private long mNativeMeshWrapper;
+ private boolean mIsIndexed;
+
+ /**
+ * Enum to determine how the mesh is represented.
+ */
+ public enum Mode {Triangles, TriangleStrip}
+
+ private static class MeshHolder {
+ public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
+ NativeAllocationRegistry.createMalloced(
+ MeshSpecification.class.getClassLoader(), nativeGetFinalizer());
+ }
+
+ /**
+ * Generates a {@link Mesh} object.
+ *
+ * @param meshSpec {@link MeshSpecification} used when generating the mesh.
+ * @param mode {@link Mode} enum
+ * @param vertexBuffer vertex buffer representing through {@link Buffer}.
+ * @param vertexCount the number of vertices represented in the vertexBuffer.
+ * @param bounds bounds of the mesh object.
+ * @return a new Mesh object.
+ */
+ public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
+ int vertexCount, Rect bounds) {
+ long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+ vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left,
+ bounds.top, bounds.right, bounds.bottom);
+ if (nativeMesh == 0) {
+ throw new IllegalArgumentException("Mesh construction failed.");
+ }
+ return new Mesh(nativeMesh, false);
+ }
+
+ /**
+ * Generates an indexed {@link Mesh} object.
+ *
+ * @param meshSpec {@link MeshSpecification} used when generating the mesh.
+ * @param mode {@link Mode} enum
+ * @param vertexBuffer vertex buffer representing through {@link Buffer}.
+ * @param vertexCount the number of vertices represented in the vertexBuffer.
+ * @param indexBuffer index buffer representing through {@link ShortBuffer}.
+ * @param bounds bounds of the mesh object.
+ * @return a new Mesh object.
+ */
+ public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
+ int vertexCount, ShortBuffer indexBuffer, Rect bounds) {
+ long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+ vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer,
+ indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left,
+ bounds.top, bounds.right, bounds.bottom);
+ if (nativeMesh == 0) {
+ throw new IllegalArgumentException("Mesh construction failed.");
+ }
+ return new Mesh(nativeMesh, true);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the color uniform declared in the shader program.
+ * @param color the provided sRGB color will be converted into the shader program's output
+ * colorspace and be available as a vec4 uniform in the program.
+ */
+ public void setColorUniform(String uniformName, int color) {
+ setUniform(uniformName, Color.valueOf(color).getComponents(), true);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the color uniform declared in the shader program.
+ * @param color the provided sRGB color will be converted into the shader program's output
+ * colorspace and be available as a vec4 uniform in the program.
+ */
+ public void setColorUniform(String uniformName, long color) {
+ Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+ setUniform(uniformName, exSRGB.getComponents(), true);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the color uniform declared in the shader program.
+ * @param color the provided sRGB color will be converted into the shader program's output
+ * colorspace and will be made available as a vec4 uniform in the program.
+ */
+ public void setColorUniform(String uniformName, Color color) {
+ if (color == null) {
+ throw new NullPointerException("The color parameter must not be null");
+ }
+
+ Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+ setUniform(uniformName, exSRGB.getComponents(), true);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the float uniform declared in the shader program.
+ * @param value float value corresponding to the float uniform with the given name.
+ */
+ public void setFloatUniform(String uniformName, float value) {
+ setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the float uniform declared in the shader program.
+ * @param value1 first float value corresponding to the float uniform with the given name.
+ * @param value2 second float value corresponding to the float uniform with the given name.
+ */
+ public void setFloatUniform(String uniformName, float value1, float value2) {
+ setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the float uniform declared in the shader program.
+ * @param value1 first float value corresponding to the float uniform with the given name.
+ * @param value2 second float value corresponding to the float uniform with the given name.
+ * @param value3 third float value corresponding to the float unifiform with the given
+ * name.
+ */
+ public void setFloatUniform(String uniformName, float value1, float value2, float value3) {
+ setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the float uniform declared in the shader program.
+ * @param value1 first float value corresponding to the float uniform with the given name.
+ * @param value2 second float value corresponding to the float uniform with the given name.
+ * @param value3 third float value corresponding to the float uniform with the given name.
+ * @param value4 fourth float value corresponding to the float uniform with the given name.
+ */
+ public void setFloatUniform(
+ String uniformName, float value1, float value2, float value3, float value4) {
+ setFloatUniform(uniformName, value1, value2, value3, value4, 4);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the float uniform declared in the shader program.
+ * @param values float value corresponding to the vec4 float uniform with the given name.
+ */
+ public void setFloatUniform(String uniformName, float[] values) {
+ setUniform(uniformName, values, false);
+ }
+
+ private void setFloatUniform(
+ String uniformName, float value1, float value2, float value3, float value4, int count) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+ nativeUpdateUniforms(
+ mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
+ nativeUpdateMesh(mNativeMeshWrapper);
+ }
+
+ private void setUniform(String uniformName, float[] values, boolean isColor) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+ if (values == null) {
+ throw new NullPointerException("The uniform values parameter must not be null");
+ }
+
+ nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values, isColor);
+ nativeUpdateMesh(mNativeMeshWrapper);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param value value corresponding to the int uniform with the given name.
+ */
+ public void setIntUniform(String uniformName, int value) {
+ setIntUniform(uniformName, value, 0, 0, 0, 1);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param value1 first value corresponding to the int uniform with the given name.
+ * @param value2 second value corresponding to the int uniform with the given name.
+ */
+ public void setIntUniform(String uniformName, int value1, int value2) {
+ setIntUniform(uniformName, value1, value2, 0, 0, 2);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param value1 first value corresponding to the int uniform with the given name.
+ * @param value2 second value corresponding to the int uniform with the given name.
+ * @param value3 third value corresponding to the int uniform with the given name.
+ */
+ public void setIntUniform(String uniformName, int value1, int value2, int value3) {
+ setIntUniform(uniformName, value1, value2, value3, 0, 3);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param value1 first value corresponding to the int uniform with the given name.
+ * @param value2 second value corresponding to the int uniform with the given name.
+ * @param value3 third value corresponding to the int uniform with the given name.
+ * @param value4 fourth value corresponding to the int uniform with the given name.
+ */
+ public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) {
+ setIntUniform(uniformName, value1, value2, value3, value4, 4);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ *
+ * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param values int values corresponding to the vec4 int uniform with the given name.
+ */
+ public void setIntUniform(String uniformName, int[] values) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+ if (values == null) {
+ throw new NullPointerException("The uniform values parameter must not be null");
+ }
+ nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values);
+ nativeUpdateMesh(mNativeMeshWrapper);
+ }
+
+ private void setIntUniform(
+ String uniformName, int value1, int value2, int value3, int value4, int count) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+
+ nativeUpdateUniforms(
+ mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
+ nativeUpdateMesh(mNativeMeshWrapper);
+ }
+
+ private Mesh(long nativeMeshWrapper, boolean isIndexed) {
+ mNativeMeshWrapper = nativeMeshWrapper;
+ this.mIsIndexed = isIndexed;
+ MeshHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(this, mNativeMeshWrapper);
+ }
+
+ private static native long nativeGetFinalizer();
+
+ private static native long nativeMake(long meshSpec, int mode, Buffer vertexBuffer,
+ boolean isDirect, int vertexCount, int vertexOffset, int left, int top, int right,
+ int bottom);
+
+ private static native long nativeMakeIndexed(long meshSpec, int mode, Buffer vertexBuffer,
+ boolean isVertexDirect, int vertexCount, int vertexOffset, ShortBuffer indexBuffer,
+ boolean isIndexDirect, int indexCount, int indexOffset, int left, int top, int right,
+ int bottom);
+
+ private static native void nativeUpdateUniforms(long builder, String uniformName, float value1,
+ float value2, float value3, float value4, int count);
+
+ private static native void nativeUpdateUniforms(
+ long builder, String uniformName, float[] values, boolean isColor);
+
+ private static native void nativeUpdateUniforms(long builder, String uniformName, int value1,
+ int value2, int value3, int value4, int count);
+
+ private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values);
+
+ private static native void nativeUpdateMesh(long nativeMeshWrapper);
+}
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index b27c5e0..45c13af 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -39,7 +39,7 @@
* @hide
*/
public class MeshSpecification {
- private long mNativeMeshSpec;
+ long mNativeMeshSpec;
/**
* Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
new file mode 100644
index 0000000..6dba6c1
--- /dev/null
+++ b/libs/hwui/jni/Mesh.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#include <GLES/gl.h>
+#include <Mesh.h>
+#include <SkMesh.h>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size,
+ jboolean isDirect) {
+ auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
+ auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size);
+ return vertexBuffer;
+}
+
+sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
+ jboolean isDirect) {
+ auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
+ auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size);
+ return indexBuffer;
+}
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+ jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top,
+ jint right, jint bottom) {
+ auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+ sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
+ genVertexBuffer(env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isDirect);
+ auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+ auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
+ vertexOffset, nullptr, skRect);
+ auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+ return reinterpret_cast<jlong>(meshPtr.release());
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+ jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+ jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+ jint indexOffset, jint left, jint top, jint right, jint bottom) {
+ auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+ sk_sp<SkMesh::VertexBuffer> skVertexBuffer = genVertexBuffer(
+ env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isVertexDirect);
+ sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
+ genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
+ auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+ auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
+ vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
+ skRect);
+ auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+ return reinterpret_cast<jlong>(meshPtr.release());
+}
+
+static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) {
+ auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
+ auto mesh = wrapper->mesh;
+ if (indexed) {
+ wrapper->mesh = SkMesh::MakeIndexed(
+ sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+ mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
+ mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+ } else {
+ wrapper->mesh = SkMesh::Make(
+ sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+ mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+ }
+}
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+ va_end(args);
+ return ret;
+}
+
+static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+ switch (type) {
+ case SkRuntimeEffect::Uniform::Type::kFloat:
+ case SkRuntimeEffect::Uniform::Type::kFloat2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4:
+ case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+ return false;
+ case SkRuntimeEffect::Uniform::Type::kInt:
+ case SkRuntimeEffect::Uniform::Type::kInt2:
+ case SkRuntimeEffect::Uniform::Type::kInt3:
+ case SkRuntimeEffect::Uniform::Type::kInt4:
+ return true;
+ }
+}
+
+static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+ const char* uniformName, const float values[], int count,
+ bool isColor) {
+ MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+ if (isColor) {
+ jniThrowExceptionFmt(
+ env, "java/lang/IllegalArgumentException",
+ "attempting to set a color uniform using the non-color specific APIs: %s %x",
+ uniformName, uniform.fVar->flags);
+ } else {
+ ThrowIAEFmt(env,
+ "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+ uniformName);
+ }
+ } else if (isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<float>(values, count)) {
+ ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
+ }
+}
+
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+ jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+ jint count) {
+ auto* builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ ScopedUtfChars name(env, uniformName);
+ const float values[4] = {value1, value2, value3, value4};
+ nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName,
+ jfloatArray jvalues, jboolean isColor) {
+ auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ ScopedUtfChars name(env, jUniformName);
+ AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+ nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(),
+ isColor);
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+ const char* uniformName, const int values[], int count) {
+ MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (!isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<int>(values, count)) {
+ ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
+ }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+ jint value1, jint value2, jint value3, jint value4, jint count) {
+ auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ ScopedUtfChars name(env, uniformName);
+ const int values[4] = {value1, value2, value3, value4};
+ nativeUpdateIntUniforms(env, builder, name.c_str(), values, count);
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+ jintArray values) {
+ auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ ScopedUtfChars name(env, uniformName);
+ AutoJavaIntArray autoValues(env, values, 0);
+ nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+}
+
+static void MeshWrapper_destroy(MeshWrapper* wrapper) {
+ delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+ {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+ {"nativeMake", "(JILjava/nio/Buffer;ZIIIIII)J", (void*)make},
+ {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIIIII)J",
+ (void*)makeIndexed},
+ {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+ android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+ return 0;
+}
+
+} // namespace android
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
new file mode 100644
index 0000000..aa014a5
--- /dev/null
+++ b/libs/hwui/jni/Mesh.h
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
+#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
+
+#include <SkMesh.h>
+#include <jni.h>
+
+#include <utility>
+
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
+// direct and indrect buffers, allowing access to the underlying data in both
+// situations. If passed a null buffer, we will throw NullPointerException,
+// and c_data will return nullptr.
+//
+// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
+// conversion.
+class ScopedJavaNioBuffer {
+public:
+ ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect)
+ : mEnv(env), mBuffer(buffer) {
+ if (buffer == nullptr) {
+ mDataBase = nullptr;
+ mData = nullptr;
+ jniThrowNullPointerException(env);
+ } else {
+ mArray = (jarray) nullptr;
+ if (isDirect) {
+ mData = getDirectBufferPointer(mEnv, mBuffer);
+ } else {
+ mData = setIndirectData(size);
+ }
+ }
+ }
+
+ ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
+
+ ~ScopedJavaNioBuffer() { reset(); }
+
+ void reset() {
+ if (mDataBase) {
+ releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
+ mDataBase = nullptr;
+ }
+ }
+
+ ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
+ if (this != &rhs) {
+ reset();
+
+ mEnv = rhs.mEnv;
+ mBuffer = rhs.mBuffer;
+ mDataBase = rhs.mDataBase;
+ mData = rhs.mData;
+ mArray = rhs.mArray;
+ rhs.mEnv = nullptr;
+ rhs.mData = nullptr;
+ rhs.mBuffer = nullptr;
+ rhs.mArray = nullptr;
+ rhs.mDataBase = nullptr;
+ }
+ return *this;
+ }
+
+ const void* data() const { return mData; }
+
+private:
+ /**
+ * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+ * from a java.nio.Buffer.
+ */
+ void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ if (pointer == 0) {
+ jniThrowException(mEnv, "java/lang/IllegalArgumentException",
+ "Must use a native order direct Buffer");
+ return nullptr;
+ }
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+ }
+
+ static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+ env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+ }
+
+ static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
+ jint* offset) {
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ *remaining = (limit - position) << elementSizeShift;
+ if (pointer != 0L) {
+ *array = nullptr;
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+ }
+
+ *array = jniGetNioBufferBaseArray(env, buffer);
+ *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+ return nullptr;
+ }
+
+ /**
+ * This is a copy of
+ * static void android_glBufferData__IILjava_nio_Buffer_2I
+ * from com_google_android_gles_jni_GLImpl.cpp
+ */
+ void* setIndirectData(jint size) {
+ jint exception;
+ const char* exceptionType;
+ const char* exceptionMessage;
+ jint bufferOffset = (jint)0;
+ jint remaining;
+ void* tempData;
+
+ if (mBuffer) {
+ tempData =
+ (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
+ if (remaining < size) {
+ exception = 1;
+ exceptionType = "java/lang/IllegalArgumentException";
+ exceptionMessage = "remaining() < size < needed";
+ goto exit;
+ }
+ }
+ if (mBuffer && tempData == nullptr) {
+ mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
+ tempData = (void*)(mDataBase + bufferOffset);
+ }
+ return tempData;
+ exit:
+ if (mArray) {
+ releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
+ }
+ if (exception) {
+ jniThrowException(mEnv, exceptionType, exceptionMessage);
+ }
+ return nullptr;
+ }
+
+ JNIEnv* mEnv;
+
+ // Java Buffer data
+ void* mData;
+ jobject mBuffer;
+
+ // Indirect Buffer Data
+ jarray mArray;
+ char* mDataBase;
+};
+
+class MeshUniformBuilder {
+public:
+ struct MeshUniform {
+ template <typename T>
+ std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+ const T& val) {
+ if (!fVar) {
+ SkDEBUGFAIL("Assigning to missing variable");
+ } else if (sizeof(val) != fVar->sizeInBytes()) {
+ SkDEBUGFAIL("Incorrect value size");
+ } else {
+ memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), &val,
+ szeof(val));
+ }
+ }
+
+ MeshUniform& operator=(const SkMatrix& val) {
+ if (!fVar) {
+ SkDEBUGFAIL("Assigning to missing variable");
+ } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+ SkDEBUGFAIL("Incorrect value size");
+ } else {
+ float* data =
+ SkTAddOffset<float>(fOwner->writableUniformData(), (ptrdiff_t)fVar->offset);
+ data[0] = val.get(0);
+ data[1] = val.get(3);
+ data[2] = val.get(6);
+ data[3] = val.get(1);
+ data[4] = val.get(4);
+ data[5] = val.get(7);
+ data[6] = val.get(2);
+ data[7] = val.get(5);
+ data[8] = val.get(8);
+ }
+ return *this;
+ }
+
+ template <typename T>
+ bool set(const T val[], const int count) {
+ static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+ if (!fVar) {
+ SkDEBUGFAIL("Assigning to missing variable");
+ return false;
+ } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+ SkDEBUGFAIL("Incorrect value size");
+ return false;
+ } else {
+ memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), val,
+ sizeof(T) * count);
+ }
+ return true;
+ }
+
+ MeshUniformBuilder* fOwner;
+ const SkRuntimeEffect::Uniform* fVar;
+ };
+ MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+ explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+ fMeshSpec = sk_sp(meshSpec);
+ }
+
+ sk_sp<SkData> fUniforms;
+
+private:
+ void* writableUniformData() {
+ if (!fUniforms->unique()) {
+ fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+ }
+ return fUniforms->writable_data();
+ }
+
+ sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+struct MeshWrapper {
+ SkMesh mesh;
+ MeshUniformBuilder builder;
+};
+#endif // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 980f63b..59a0f7b 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -221,13 +221,6 @@
/**
* @hide
- * Mute state used for the initial state and when API is accessed without permission.
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public static final int MUTED_BY_UNKNOWN = -1;
- /**
- * @hide
* Flag used when muted by master volume.
*/
@SystemApi
@@ -317,7 +310,7 @@
mPlayerType = pic.mPlayerType;
mClientUid = uid;
mClientPid = pid;
- mMutedState = MUTED_BY_UNKNOWN;
+ mMutedState = 0;
mDeviceId = PLAYER_DEVICEID_INVALID;
mPlayerState = PLAYER_STATE_IDLE;
mPlayerAttr = pic.mAttributes;
@@ -366,7 +359,7 @@
anonymCopy.mPlayerAttr = builder.build();
anonymCopy.mDeviceId = in.mDeviceId;
// anonymized data
- anonymCopy.mMutedState = MUTED_BY_UNKNOWN;
+ anonymCopy.mMutedState = 0;
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -435,14 +428,13 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public boolean isMuted() {
- return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN;
+ return mMutedState != 0;
}
/**
* @hide
* Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags.
- * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A
- * value of 0 represents a player which is not muted.
+ * <br>A value of 0 corresponds to an unmuted player.
* @return the mute state.
*/
@SystemApi
@@ -602,11 +594,6 @@
}
private boolean isMuteAffectingActiveState() {
- if (mMutedState == MUTED_BY_UNKNOWN) {
- // mute state is not set, therefore it will not affect the active state
- return false;
- }
-
return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
|| (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
|| (mMutedState & MUTED_BY_APP_OPS) != 0;
@@ -726,9 +713,7 @@
"/").append(mClientPid).append(" state:").append(
toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
" sessionId:").append(mSessionId).append(" mutedState:");
- if (mMutedState == MUTED_BY_UNKNOWN) {
- apcToString.append("unknown ");
- } else if (mMutedState == 0) {
+ if (mMutedState == 0) {
apcToString.append("none ");
} else {
if ((mMutedState & MUTED_BY_MASTER) != 0) {
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index d51f1e1..c2c752e 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1233,18 +1233,6 @@
}
/**
- * Sets the tuner configuration for the {@code AudioTrack}.
- *
- * The {@link AudioTrack.TunerConfiguration} consists of parameters obtained from
- * the Android TV tuner API which indicate the audio content stream id and the
- * synchronization id for the {@code AudioTrack}.
- *
- * @param tunerConfiguration obtained by {@link AudioTrack.TunerConfiguration.Builder}.
- * @return the same Builder instance.
- * @hide
- */
-
- /**
* @hide
* Sets the {@link AudioTrack} call redirection mode.
* Used when creating an AudioTrack to inject audio to call uplink path. The mode
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 8a03afb..d6fe6825 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -86,8 +86,10 @@
*
* <p>
* The format is one of the values from
- * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
- * formats and the planes is as follows:
+ * {@link android.graphics.ImageFormat ImageFormat},
+ * {@link android.graphics.PixelFormat PixelFormat}, or
+ * {@link android.hardware.HardwareBuffer HardwareBuffer}. The mapping between the
+ * formats and the planes is as follows (any formats not listed will have 1 plane):
* </p>
*
* <table>
@@ -171,15 +173,18 @@
* </tr>
* <tr>
* <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td>
- * <td>1</td>
+ * <td>3</td>
* <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
- * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
- * little-endian value, with the lower 6 bits set to zero.
+ * followed by a Wx(H/2) Cb and Cr planes. Each sample is represented by a 16-bit
+ * little-endian value, with the lower 6 bits set to zero. Since this is guaranteed to be
+ * a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane.
* </td>
* </tr>
* </table>
*
* @see android.graphics.ImageFormat
+ * @see android.graphics.PixelFormat
+ * @see android.hardware.HardwareBuffer
*/
public abstract int getFormat();
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 538e64c..e78dc31 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -89,6 +89,7 @@
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
+ private boolean mPreferBuiltinDevice;
// playback properties, use synchronized with mPlaybackSettingsLock
private boolean mIsLooping = false;
private float mVolume = 1.0f;
@@ -157,6 +158,37 @@
}
/**
+ * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
+ * the one on which outgoing audio for SIM calls is played.
+ *
+ * @param audioManager the audio manage.
+ * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
+ * none can be found.
+ */
+ private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
+ AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : deviceList) {
+ if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ return device;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the preferred device of the ringtong playback to the built-in device.
+ *
+ * @hide
+ */
+ public boolean preferBuiltinDevice(boolean enable) {
+ mPreferBuiltinDevice = enable;
+ if (mLocalPlayer == null) {
+ return true;
+ }
+ return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
+ }
+
+ /**
* Creates a local media player for the ringtone using currently set attributes.
* @return true if media player creation succeeded or is deferred,
* false if it did not succeed and can't be tried remotely.
@@ -174,6 +206,8 @@
try {
mLocalPlayer.setDataSource(mContext, mUri);
mLocalPlayer.setAudioAttributes(mAudioAttributes);
+ mLocalPlayer.setPreferredDevice(
+ mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
synchronized (mPlaybackSettingsLock) {
applyPlaybackProperties_sync();
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index fab63aa..7039a3e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -998,6 +998,7 @@
private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
private native int nativeStopScan();
private native int nativeSetLnb(Lnb lnb);
+ private native boolean nativeIsLnaSupported();
private native int nativeSetLna(boolean enable);
private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
private native Integer nativeGetAvSyncHwId(Filter filter);
@@ -1382,11 +1383,32 @@
}
/**
+ * Is Low Noise Amplifier (LNA) supported by the Tuner.
+ *
+ * <p>This API is only supported by Tuner HAL 3.0 or higher.
+ * Unsupported version would throw UnsupportedOperationException. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ *
+ * @return {@code true} if supported, otherwise {@code false}.
+ * @throws UnsupportedOperationException if the Tuner HAL version is lower than 3.0
+ * @see android.media.tv.tuner.TunerVersionChecker#TUNER_VERSION_3_0
+ */
+ public boolean isLnaSupported() {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "isLnaSupported")) {
+ throw new UnsupportedOperationException("Tuner HAL version "
+ + TunerVersionChecker.getTunerVersion() + " doesn't support this method.");
+ }
+ return nativeIsLnaSupported();
+ }
+
+ /**
* Enable or Disable Low Noise Amplifier (LNA).
*
* @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
*
- * @return result status of the operation.
+ * @return result status of the operation. {@link #RESULT_UNAVAILABLE} if the device doesn't
+ * support LNA.
*/
@Result
public int setLnaEnabled(boolean enable) {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index a028c04..2afa4d1 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1819,6 +1819,13 @@
return (int)result;
}
+bool JTuner::isLnaSupported() {
+ if (sTunerClient == nullptr) {
+ return (int)Result::NOT_INITIALIZED;
+ }
+ return sTunerClient->isLnaSupported();
+}
+
int JTuner::setLna(bool enable) {
if (sTunerClient == nullptr) {
return (int)Result::NOT_INITIALIZED;
@@ -3562,6 +3569,11 @@
return tuner->setLnb(lnbClient);
}
+static bool android_media_tv_Tuner_is_lna_supported(JNIEnv *env, jobject thiz) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->isLnaSupported();
+}
+
static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean enable) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->setLna(enable);
@@ -4810,6 +4822,7 @@
(void *)android_media_tv_Tuner_scan },
{ "nativeStopScan", "()I", (void *)android_media_tv_Tuner_stop_scan },
{ "nativeSetLnb", "(Landroid/media/tv/tuner/Lnb;)I", (void *)android_media_tv_Tuner_set_lnb },
+ { "nativeIsLnaSupported", "()Z", (void *)android_media_tv_Tuner_is_lna_supported },
{ "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
{ "nativeGetFrontendStatus", "([I)Landroid/media/tv/tuner/frontend/FrontendStatus;",
(void *)android_media_tv_Tuner_get_frontend_status },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index c74b2df..2b69e89 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -189,6 +189,7 @@
int scan(const FrontendSettings& settings, FrontendScanType scanType);
int stopScan();
int setLnb(sp<LnbClient> lnbClient);
+ bool isLnaSupported();
int setLna(bool enable);
jobject openLnbByHandle(int handle);
jobject openLnbByName(jstring name);
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 8515874..ab28fb4 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -210,4 +210,17 @@
return -1;
}
+bool TunerClient::isLnaSupported() {
+ if (mTunerService != nullptr) {
+ bool lnaSupported;
+ Status s = mTunerService->isLnaSupported(&lnaSupported);
+ if (!s.isOk()) {
+ return false;
+ }
+ return lnaSupported;
+ }
+
+ return false;
+}
+
} // namespace android
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 5410c1b..3f8b21c 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -148,6 +148,11 @@
*/
int getMaxNumberOfFrontends(FrontendType frontendType);
+ /**
+ * Is Low Noise Amplifier (LNA) supported.
+ */
+ bool isLnaSupported();
+
private:
/**
* An AIDL Tuner Service assigned at the first time the Tuner Client connects with
diff --git a/packages/CredentialManager/res/drawable/ic_other_sign_in.xml b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
new file mode 100644
index 0000000..8150197
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
@@ -0,0 +1,36 @@
+<!--
+ ~ 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.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:name="path"
+ android:pathData="M 20 19 L 12 19 L 12 21 L 20 21 C 21.1 21 22 20.1 22 19 L 22 5 C 22 3.9 21.1 3 20 3 L 12 3 L 12 5 L 20 5 L 20 19 Z"
+ android:fillColor="#000"
+ android:strokeWidth="1"/>
+ <path
+ android:name="path_1"
+ android:pathData="M 12 7 L 10.6 8.4 L 13.2 11 L 8.85 11 C 8.42 9.55 7.09 8.5 5.5 8.5 C 3.57 8.5 2 10.07 2 12 C 2 13.93 3.57 15.5 5.5 15.5 C 7.09 15.5 8.42 14.45 8.85 13 L 13.2 13 L 10.6 15.6 L 12 17 L 17 12 L 12 7 Z M 5.5 13.5 C 4.67 13.5 4 12.83 4 12 C 4 11.17 4.67 10.5 5.5 10.5 C 6.33 10.5 7 11.17 7 12 C 7 12.83 6.33 13.5 5.5 13.5 Z"
+ android:fillColor="#000"
+ android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_password.xml b/packages/CredentialManager/res/drawable/ic_password.xml
new file mode 100644
index 0000000..bf3056a
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_password.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ 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.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:name="path"
+ android:pathData="M 8.71 10.29 C 8.52 10.1 8.28 10 8 10 L 7.75 10 L 7.75 8.75 C 7.75 7.98 7.48 7.33 6.95 6.8 C 6.42 6.27 5.77 6 5 6 C 4.23 6 3.58 6.27 3.05 6.8 C 2.52 7.33 2.25 7.98 2.25 8.75 L 2.25 10 L 2 10 C 1.72 10 1.48 10.1 1.29 10.29 C 1.1 10.48 1 10.72 1 11 L 1 16 C 1 16.28 1.1 16.52 1.29 16.71 C 1.48 16.9 1.72 17 2 17 L 8 17 C 8.28 17 8.52 16.9 8.71 16.71 C 8.9 16.52 9 16.28 9 16 L 9 11 C 9 10.72 8.9 10.48 8.71 10.29 Z M 6.25 10 L 3.75 10 L 3.75 8.75 C 3.75 8.4 3.87 8.1 4.11 7.86 C 4.35 7.62 4.65 7.5 5 7.5 C 5.35 7.5 5.65 7.62 5.89 7.86 C 6.13 8.1 6.25 8.4 6.25 8.75 L 6.25 10 Z M 10 14 L 23 14 L 23 16 L 10 16 Z M 21.5 9 C 21.102 9 20.721 9.158 20.439 9.439 C 20.158 9.721 20 10.102 20 10.5 C 20 10.898 20.158 11.279 20.439 11.561 C 20.721 11.842 21.102 12 21.5 12 C 21.898 12 22.279 11.842 22.561 11.561 C 22.842 11.279 23 10.898 23 10.5 C 23 10.102 22.842 9.721 22.561 9.439 C 22.279 9.158 21.898 9 21.5 9 Z M 16.5 9 C 16.102 9 15.721 9.158 15.439 9.439 C 15.158 9.721 15 10.102 15 10.5 C 15 10.898 15.158 11.279 15.439 11.561 C 15.721 11.842 16.102 12 16.5 12 C 16.898 12 17.279 11.842 17.561 11.561 C 17.842 11.279 18 10.898 18 10.5 C 18 10.102 17.842 9.721 17.561 9.439 C 17.279 9.158 16.898 9 16.5 9 Z M 11.5 9 C 11.102 9 10.721 9.158 10.439 9.439 C 10.158 9.721 10 10.102 10 10.5 C 10 10.898 10.158 11.279 10.439 11.561 C 10.721 11.842 11.102 12 11.5 12 C 11.898 12 12.279 11.842 12.561 11.561 C 12.842 11.279 13 10.898 13 10.5 C 13 10.102 12.842 9.721 12.561 9.439 C 12.279 9.158 11.898 9 11.5 9 Z"
+ android:fillColor="#000"
+ android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 1ee2a26..252ecf4 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -1,43 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name">CredentialManager</string>
+ <!-- The name of this application. Credential Manager is a service that centralizes and provides
+ access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] -->
+ <string name="app_name">Credential Manager</string>
+
+ <!-- Strings for the create flow. -->
+ <!-- Button label to close the dialog when the user does not want to create the credential. [CHAR LIMIT=40] -->
<string name="string_cancel">Cancel</string>
+ <!-- Button label to confirm choosing the default dialog information and continue. [CHAR LIMIT=40] -->
<string name="string_continue">Continue</string>
- <string name="string_more_options">More options</string>
+ <!-- This appears as a text button where users can click to create this passkey in other available places. [CHAR LIMIT=80] -->
<string name="string_create_in_another_place">Create in another place</string>
+ <!-- This appears as a text button where users can click to create this password or other credential types in other available places. [CHAR LIMIT=80] -->
<string name="string_save_to_another_place">Save to another place</string>
+ <!-- This appears as a text button where users can click to use another device to create this credential. [CHAR LIMIT=80] -->
<string name="string_use_another_device">Use another device</string>
+ <!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] -->
<string name="string_save_to_another_device">Save to another device</string>
- <string name="string_no_thanks">No thanks</string>
+ <!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_title">A simple way to sign in safely</string>
+ <!-- This appears as the description body of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
- <string name="choose_provider_title">Choose where to <xliff:g id="createTypes">%1$s</xliff:g></string>
- <!-- TODO: Change the wording after design is completed. -->
- <string name="choose_provider_body">This password manager will store your passwords, passkeys, and other sign-in info to help you easily sign in. You can change where to save your sign-in info at any time.</string>
- <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
- <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
- <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
- <string name="choose_sign_in_title">Use saved sign in</string>
- <string name="create_your_passkey">create your passkey</string>
+ <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
+ <string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
+ <!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
+ <string name="create_your_passkeys">create your passkeys</string>
<string name="save_your_password">save your password</string>
<string name="save_your_sign_in_info">save your sign-in info</string>
- <string name="create_passkey_in">Create passkey in</string>
- <string name="save_password_to">Save password to</string>
- <string name="save_sign_in_to">Save sign-in to</string>
- <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string>
- <string name="set_as_default">Set as default</string>
- <string name="use_once">Use once</string>
- <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName">%1$s</xliff:g> <xliff:g id="type">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName">%4$s</xliff:g></string>
- <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber">%2$s</xliff:g> passkeys</string>
- <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords</string>
- <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber">%1$s</xliff:g> passkeys</string>
+
+ <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
+ <string name="choose_provider_body">Set a default password manager to save your passwords and passkeys and sign in faster next time.</string>
+ <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] -->
+ <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+ <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] -->
+ <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+ <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
+ <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+ <!-- This appears as the description body of the modal bottom sheet for users to choose the create option inside a provider. [CHAR LIMIT=200] -->
+ <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="type" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g></string>
+ <!-- Types which are inserted as a placeholder for string choose_create_option_description. [CHAR LIMIT=200] -->
<string name="passkey">passkey</string>
<string name="password">password</string>
<string name="sign_ins">sign-ins</string>
- <string name="another_device">Another device</string>
- <string name="other_password_manager">Other password managers</string>
+
+ <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created passkey can be created to. [CHAR LIMIT=200] -->
+ <string name="create_passkey_in_title">Create passkey in</string>
+ <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created password can be saved to. [CHAR LIMIT=200] -->
+ <string name="save_password_to_title">Save password to</string>
+ <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
+ <string name="save_sign_in_to_title">Save sign-in to</string>
+ <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
+ <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
<!-- TODO: Check the wording here. -->
- <string name="confirm_default_or_use_once_description">This password manager will store your passwords and passkeys to help you easily sign in.</string>
+ <!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
+ <string name="use_provider_for_all_description">This password manager will store your passwords and passkeys to help you easily sign in.</string>
+ <!-- Button label to set the selected provider on the modal bottom sheet as default. [CHAR LIMIT=40] -->
+ <string name="set_as_default">Set as default</string>
+ <!-- Button label to set the selected provider on the modal bottom sheet not as default but just use once. [CHAR LIMIT=40] -->
+ <string name="use_once">Use once</string>
+ <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
+ <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
+ <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passwords. [CHAR LIMIT=80] -->
+ <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber" example="3">%1$s</xliff:g> passwords</string>
+ <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passkeys. [CHAR LIMIT=80] -->
+ <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber" example="4">%1$s</xliff:g> passkeys</string>
+ <!-- Appears before a request display name when the credential type is passkey . [CHAR LIMIT=80] -->
+ <string name="passkey_before_subtitle">Passkey</string>
+ <!-- Appears as an option row title that users can choose to use another device for this creation. [CHAR LIMIT=80] -->
+ <string name="another_device">Another device</string>
+ <!-- Appears as an option row title that users can choose to view other disabled providers. [CHAR LIMIT=80] -->
+ <string name="other_password_manager">Other password managers</string>
<!-- Spoken content description of an element which will close the sheet when clicked. -->
<string name="close_sheet">"Close sheet"</string>
<!-- Spoken content description of the back arrow button. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 2bede9a..530f1c4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -39,16 +39,15 @@
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
+import android.service.credentials.CredentialProviderService
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
-import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
-import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest.Companion.createFrom
-import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
// Consider repo per screen, similar to view model?
@@ -66,7 +65,7 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testCreateRequestInfo()
+ ) ?: testCreatePasskeyRequestInfo()
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
@@ -136,9 +135,10 @@
}
fun createCredentialInitialUiState(): CreateCredentialUiState {
+ val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
// Handle runtime cast error
- providerEnabledList as List<CreateCredentialProviderData>, context)
+ providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
providerDisabledList as List<DisabledProviderData>, context)
@@ -147,21 +147,6 @@
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
- // TODO: covert from real requestInfo for create passkey
- var requestDisplayInfo = RequestDisplayInfo(
- "beckett-bakert@gmail.com",
- "Elisa Beckett",
- TYPE_PUBLIC_KEY_CREDENTIAL,
- "tribank")
- val createCredentialRequest = requestInfo.createCredentialRequest
- val createCredentialRequestJetpack = createCredentialRequest?.let { createFrom(it) }
- if (createCredentialRequestJetpack is CreatePasswordRequest) {
- requestDisplayInfo = RequestDisplayInfo(
- createCredentialRequestJetpack.id,
- createCredentialRequestJetpack.password,
- TYPE_PASSWORD_CREDENTIAL,
- "tribank")
- }
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
@@ -390,11 +375,12 @@
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
or PendingIntent.FLAG_ONE_SHOT))
val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
- context.applicationInfo.packageName,
- "PASSWORD",
- toBundle("beckett-bakert@gmail.com", "password123")
+ context.applicationInfo.packageName,
+ TYPE_PASSWORD_CREDENTIAL,
+ toBundle("beckett-bakert@gmail.com", "password123")
)
- val fillInIntent = Intent().putExtra("create_request_params", createPasswordRequest)
+ val fillInIntent = Intent().putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
+ createPasswordRequest)
val slice = Slice.Builder(
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
@@ -438,8 +424,42 @@
)
}
- private fun testCreateRequestInfo(): RequestInfo {
- val data = toBundle("beckett-bakert@gmail.com", "password123")
+ private fun testCreatePasskeyRequestInfo(): RequestInfo {
+ val request = CreatePublicKeyCredentialRequest("{\"extensions\": {\n" +
+ " \"webauthn.loc\": true\n" +
+ " },\n" +
+ " \"attestation\": \"direct\",\n" +
+ " \"challenge\": \"-rSQHXSQUdaK1N-La5bE-JPt6EVAW4SxX1K_tXhZ_Gk\",\n" +
+ " \"user\": {\n" +
+ " \"displayName\": \"testName\",\n" +
+ " \"name\": \"credManTesting@gmail.com\",\n" +
+ " \"id\": \"eD4o2KoXLpgegAtnM5cDhhUPvvk2\"\n" +
+ " },\n" +
+ " \"excludeCredentials\": [],\n" +
+ " \"rp\": {\n" +
+ " \"name\": \"Address Book\",\n" +
+ " \"id\": \"addressbook-c7876.uc.r.appspot.com\"\n" +
+ " },\n" +
+ " \"timeout\": 60000,\n" +
+ " \"pubKeyCredParams\": [\n" +
+ " {\n" +
+ " \"type\": \"public-key\",\n" +
+ " \"alg\": -7\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\": \"public-key\",\n" +
+ " \"alg\": -257\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\": \"public-key\",\n" +
+ " \"alg\": -37\n" +
+ " }\n" +
+ " ],\n" +
+ " \"authenticatorSelection\": {\n" +
+ " \"residentKey\": \"required\",\n" +
+ " \"requireResidentKey\": true\n" +
+ " }}")
+ val data = request.data
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
@@ -451,6 +471,32 @@
)
}
+ private fun testCreatePasswordRequestInfo(): RequestInfo {
+ val data = toBundle("beckett-bakert@gmail.com", "password123")
+ return RequestInfo.newCreateRequestInfo(
+ Binder(),
+ CreateCredentialRequest(
+ TYPE_PASSWORD_CREDENTIAL,
+ data
+ ),
+ /*isFirstUsage=*/false,
+ "tribank"
+ )
+ }
+
+ private fun testCreateOtherCredentialRequestInfo(): RequestInfo {
+ val data = Bundle()
+ return RequestInfo.newCreateRequestInfo(
+ Binder(),
+ CreateCredentialRequest(
+ "other-sign-ins",
+ data
+ ),
+ /*isFirstUsage=*/false,
+ "tribank"
+ )
+ }
+
private fun testGetRequestInfo(): RequestInfo {
return RequestInfo.newGetRequestInfo(
Binder(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 2eb3284..b96f686 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -23,17 +23,23 @@
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.DisabledProviderData
+import android.credentials.ui.RequestInfo
import android.graphics.drawable.Drawable
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
import com.android.credentialmanager.getflow.CredentialEntryInfo
import com.android.credentialmanager.getflow.ProviderInfo
import com.android.credentialmanager.getflow.RemoteEntryInfo
+import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.provider.ActionUi
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
+import org.json.JSONObject
/** Utility functions for converting CredentialManager data structures to or from UI formats. */
class GetFlowUtils {
@@ -172,6 +178,7 @@
fun toEnabledProviderList(
providerDataList: List<CreateCredentialProviderData>,
+ requestDisplayInfo: RequestDisplayInfo,
context: Context,
): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
// TODO: get from the actual service info
@@ -194,7 +201,7 @@
name = it.providerFlattenedComponentName,
displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
createOptions = toCreationOptionInfoList(
- it.providerFlattenedComponentName, it.saveEntries, context),
+ it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
isDefault = it.isDefaultProvider,
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
)
@@ -219,9 +226,59 @@
}
}
+ fun toRequestDisplayInfo(
+ requestInfo: RequestInfo,
+ context: Context,
+ ): RequestDisplayInfo {
+ val createCredentialRequest = requestInfo.createCredentialRequest
+ val createCredentialRequestJetpack = createCredentialRequest?.let {
+ CreateCredentialRequest.createFrom(
+ it
+ )
+ }
+ when (createCredentialRequestJetpack) {
+ is CreatePasswordRequest -> {
+ return RequestDisplayInfo(
+ createCredentialRequestJetpack.id,
+ createCredentialRequestJetpack.password,
+ createCredentialRequestJetpack.type,
+ requestInfo.appPackageName,
+ context.getDrawable(R.drawable.ic_password)!!
+ )
+ }
+ is CreatePublicKeyCredentialRequest -> {
+ val requestJson = createCredentialRequestJetpack.requestJson
+ val json = JSONObject(requestJson)
+ var name = ""
+ var displayName = ""
+ if (json.has("user")) {
+ val user: JSONObject = json.getJSONObject("user")
+ name = user.getString("name")
+ displayName = user.getString("displayName")
+ }
+ return RequestDisplayInfo(
+ name,
+ displayName,
+ createCredentialRequestJetpack.type,
+ requestInfo.appPackageName,
+ context.getDrawable(R.drawable.ic_passkey)!!)
+ }
+ // TODO: correctly parsing for other sign-ins
+ else -> {
+ return RequestDisplayInfo(
+ "beckett-bakert@gmail.com",
+ "Elisa Beckett",
+ "other-sign-ins",
+ requestInfo.appPackageName,
+ context.getDrawable(R.drawable.ic_other_sign_in)!!)
+ }
+ }
+ }
+
private fun toCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,
+ requestDisplayInfo: RequestDisplayInfo,
context: Context,
): List<CreateOptionInfo> {
return creationEntries.map {
@@ -236,7 +293,7 @@
fillInIntent = it.frameworkExtrasIntent,
userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
- ?: context.getDrawable(R.drawable.ic_profile)!!,
+ ?: requestDisplayInfo.typeIcon,
passwordCount = saveEntryUi.passwordCount ?: 0,
passkeyCount = saveEntryUi.passkeyCount ?: 0,
totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 9f73aef..fde3279 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -191,19 +191,18 @@
) {
Card() {
Column() {
- // TODO: Change the icon for create passwords and sign-ins
Icon(
- painter = painterResource(R.drawable.ic_passkey),
+ bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(top = 24.dp, bottom = 16.dp)
+ .padding(top = 24.dp, bottom = 16.dp).size(32.dp)
)
Text(
text = stringResource(
R.string.choose_provider_title,
when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkey)
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkeys)
TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_your_password)
else -> stringResource(R.string.save_your_sign_in_info)
},
@@ -274,6 +273,10 @@
}
}
}
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
Row(
horizontalArrangement = Arrangement.Start,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
@@ -306,9 +309,9 @@
title = {
Text(
text = when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to)
- else -> stringResource(R.string.save_sign_in_to)
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in_title)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to_title)
+ else -> stringResource(R.string.save_sign_in_to_title)
},
style = MaterialTheme.typography.titleMedium
)
@@ -399,7 +402,7 @@
textAlign = TextAlign.Center,
)
Text(
- text = stringResource(R.string.confirm_default_or_use_once_description),
+ text = stringResource(R.string.use_provider_for_all_description),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
@@ -567,35 +570,52 @@
Entry(
onClick = {onOptionSelected(createOptionInfo)},
icon = {
- // TODO: Upload the other two types icons and change it according to request types
Icon(
- painter = painterResource(R.drawable.ic_passkey),
+ bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- modifier = Modifier.padding(start = 18.dp)
+ modifier = Modifier.padding(start = 18.dp).size(32.dp)
)
},
label = {
Column() {
// TODO: Add the function to hide/view password when the type is create password
- if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
- requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
- Text(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = requestDisplayInfo.subtitle,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- } else {
- Text(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
- )
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = if (requestDisplayInfo.subtitle != null) {
+ stringResource(
+ R.string.passkey_before_subtitle) + " - " + requestDisplayInfo.subtitle
+ } else {stringResource(R.string.passkey_before_subtitle)},
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ TYPE_PASSWORD_CREDENTIAL -> {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ // This subtitle would never be null for create password
+ text = requestDisplayInfo.subtitle ?: "",
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ else -> {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
+ )
+ }
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 6dd6afb..31d0365 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -73,9 +73,10 @@
data class RequestDisplayInfo(
val title: String,
- val subtitle: String,
+ val subtitle: String?,
val type: String,
val appDomainName: String,
+ val typeIcon: Drawable,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index db0c16c..8ccdf4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -167,7 +167,7 @@
horizontalArrangement = Arrangement.Start,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- CancelButton(stringResource(R.string.string_no_thanks), onCancel)
+ CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
}
Divider(
thickness = 18.dp,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
index 26d61f9..37a4f76 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
@@ -47,7 +47,7 @@
return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
CreatePublicKeyCredentialRequest
.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
- CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
+ CreatePublicKeyCredentialRequest.createFrom(data)
CreatePublicKeyCredentialRequestPrivileged
.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED ->
CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 5c796af..a36cbc0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -115,12 +115,24 @@
}
List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+ int resId = 0;
for (LocalBluetoothProfile profile : profiles) {
- int resId = profile.getDrawableResource(btClass);
- if (resId != 0) {
- return new Pair<>(getBluetoothDrawable(context, resId), null);
+ int profileResId = profile.getDrawableResource(btClass);
+ if (profileResId != 0) {
+ // The device should show hearing aid icon if it contains any hearing aid related
+ // profiles
+ if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
+ return new Pair<>(getBluetoothDrawable(context, profileResId), null);
+ }
+ if (resId == 0) {
+ resId = profileResId;
+ }
}
}
+ if (resId != 0) {
+ return new Pair<>(getBluetoothDrawable(context, resId), null);
+ }
+
if (btClass != null) {
if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
return new Pair<>(
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index b929f8c..61c7fb9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1164,14 +1164,22 @@
// Try to show left/right information if can not get it from battery for hearing
// aids specifically.
- if (mIsActiveDeviceHearingAid
+ boolean isActiveAshaHearingAid = mIsActiveDeviceHearingAid;
+ boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
+ && isConnectedHapClientDevice();
+ if ((isActiveAshaHearingAid || isActiveLeAudioHearingAid)
&& stringRes == R.string.bluetooth_active_no_battery_level) {
+ final Set<CachedBluetoothDevice> memberDevices = getMemberDevice();
final CachedBluetoothDevice subDevice = getSubDevice();
- if (subDevice != null && subDevice.isConnected()) {
+ if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
+ stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
+ } else if (subDevice != null && subDevice.isConnected()) {
stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
} else {
int deviceSide = getDeviceSide();
- if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
+ if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
+ stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
+ } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
stringRes = R.string.bluetooth_hearing_aid_left_active;
} else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
stringRes = R.string.bluetooth_hearing_aid_right_active;
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 132a631..8b68a09 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -66,7 +66,7 @@
public BatteryStatus(Intent batteryChangedIntent) {
status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
- level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+ level = getBatteryLevel(batteryChangedIntent);
health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
@@ -188,7 +188,7 @@
*/
public static boolean isCharged(Intent batteryChangedIntent) {
int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
- int level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+ int level = getBatteryLevel(batteryChangedIntent);
return isCharged(status, level);
}
@@ -204,4 +204,13 @@
public static boolean isCharged(int status, int level) {
return status == BATTERY_STATUS_FULL || level >= 100;
}
+
+ /** Gets the battery level from the intent. */
+ public static int getBatteryLevel(Intent batteryChangedIntent) {
+ final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+ return scale == 0
+ ? -1 /*invalid battery level*/
+ : Math.round((level / (float) scale) * 100f);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 336cdd3..291f6a3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -317,7 +317,9 @@
@Test
public void getBatteryStatus_statusIsFull_returnFullString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+ final Intent intent = new Intent()
+ .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+ .putExtra(BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
@@ -326,7 +328,9 @@
@Test
public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+ final Intent intent = new Intent()
+ .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+ .putExtra(BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 1f518ec..65671a2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -29,6 +29,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
@@ -447,6 +448,26 @@
}
@Test
+ public void getConnectionSummary_testActiveDeviceLeAudioHearingAid() {
+ // Test without battery level
+ // Set HAP Client and LE Audio profile to be connected and test connection state summary
+ when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+ updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
+ updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+ // Set device as Active for LE Audio and test connection state summary
+ mCachedDevice.setHearingAidInfo(getLeftLeAudioHearingAidInfo());
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.LE_AUDIO);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
+
+ // Set LE Audio profile to be disconnected and test connection state summary
+ mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.LE_AUDIO);
+ mCachedDevice.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+ }
+
+ @Test
public void getConnectionSummary_testMultipleProfilesActiveDevice() {
// Test without battery level
// Set A2DP and HFP profiles to be connected and test connection state summary
@@ -1110,9 +1131,16 @@
.setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
.build();
}
+
private HearingAidInfo getRightAshaHearingAidInfo() {
return new HearingAidInfo.Builder()
.setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT)
.build();
}
+
+ private HearingAidInfo getLeftLeAudioHearingAidInfo() {
+ return new HearingAidInfo.Builder()
+ .setLeAudioLocation(BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT)
+ .build();
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 01c0809..680a0a1 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -771,6 +771,80 @@
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE" />
+ <uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE" />
+ <uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
+ <uses-permission android:name="android.permission.health.READ_BODY_FAT" />
+ <uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.READ_BODY_WATER_MASS" />
+ <uses-permission android:name="android.permission.health.READ_BONE_MASS" />
+ <uses-permission android:name="android.permission.health.READ_CERVICAL_MUCUS" />
+ <uses-permission android:name="android.permission.health.READ_DISTANCE" />
+ <uses-permission android:name="android.permission.health.READ_ELEVATION_GAINED" />
+ <uses-permission android:name="android.permission.health.READ_EXERCISE" />
+ <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY" />
+ <uses-permission android:name="android.permission.health.READ_HEIGHT" />
+ <uses-permission android:name="android.permission.health.READ_HIP_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.READ_HYDRATION" />
+ <uses-permission android:name="android.permission.health.READ_LEAN_BODY_MASS" />
+ <uses-permission android:name="android.permission.health.READ_MENSTRUATION" />
+ <uses-permission android:name="android.permission.health.READ_NUTRITION" />
+ <uses-permission android:name="android.permission.health.READ_OVULATION_TEST" />
+ <uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION" />
+ <uses-permission android:name="android.permission.health.READ_POWER" />
+ <uses-permission android:name="android.permission.health.READ_RESPIRATORY_RATE" />
+ <uses-permission android:name="android.permission.health.READ_RESTING_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.READ_SEXUAL_ACTIVITY" />
+ <uses-permission android:name="android.permission.health.READ_SLEEP" />
+ <uses-permission android:name="android.permission.health.READ_SPEED" />
+ <uses-permission android:name="android.permission.health.READ_STEPS" />
+ <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.READ_VO2_MAX" />
+ <uses-permission android:name="android.permission.health.READ_WAIST_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.READ_WEIGHT" />
+ <uses-permission android:name="android.permission.health.READ_WHEELCHAIR_PUSHES" />
+ <uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.WRITE_BASAL_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.WRITE_BASAL_METABOLIC_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE" />
+ <uses-permission android:name="android.permission.health.WRITE_BLOOD_PRESSURE" />
+ <uses-permission android:name="android.permission.health.WRITE_BODY_FAT" />
+ <uses-permission android:name="android.permission.health.WRITE_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.WRITE_BODY_WATER_MASS" />
+ <uses-permission android:name="android.permission.health.WRITE_BONE_MASS" />
+ <uses-permission android:name="android.permission.health.WRITE_CERVICAL_MUCUS" />
+ <uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
+ <uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
+ <uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
+ <uses-permission android:name="android.permission.health.WRITE_FLOORS_CLIMBED" />
+ <uses-permission android:name="android.permission.health.WRITE_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_HEART_RATE_VARIABILITY" />
+ <uses-permission android:name="android.permission.health.WRITE_HEIGHT" />
+ <uses-permission android:name="android.permission.health.WRITE_HIP_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.WRITE_HYDRATION" />
+ <uses-permission android:name="android.permission.health.WRITE_LEAN_BODY_MASS" />
+ <uses-permission android:name="android.permission.health.WRITE_MENSTRUATION" />
+ <uses-permission android:name="android.permission.health.WRITE_NUTRITION" />
+ <uses-permission android:name="android.permission.health.WRITE_OVULATION_TEST" />
+ <uses-permission android:name="android.permission.health.WRITE_OXYGEN_SATURATION" />
+ <uses-permission android:name="android.permission.health.WRITE_POWER" />
+ <uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_SEXUAL_ACTIVITY" />
+ <uses-permission android:name="android.permission.health.WRITE_SLEEP" />
+ <uses-permission android:name="android.permission.health.WRITE_SPEED" />
+ <uses-permission android:name="android.permission.health.WRITE_STEPS" />
+ <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.WRITE_VO2_MAX" />
+ <uses-permission android:name="android.permission.health.WRITE_WAIST_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.WRITE_WEIGHT" />
+ <uses-permission android:name="android.permission.health.WRITE_WHEELCHAIR_PUSHES" />
+
<!-- Permission required for CTS test - ApplicationExemptionsTests -->
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
index e611e8b..979e1a0 100644
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -38,12 +38,18 @@
import platform.test.screenshot.getEmulatedDevicePathConfig
/** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ComposeScreenshotTestRule(
+ emulationSpec: DeviceEmulationSpec,
+ assetPathRelativeToBuildRoot: String
+) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ SystemUIGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetPathRelativeToBuildRoot
+ )
)
private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
private val delegateRule =
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 2b7bdc2..c772c96 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,7 +27,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- androidprv:layout_maxWidth="@dimen/keyguard_security_width"
+ androidprv:layout_maxWidth="@dimen/biometric_auth_pattern_view_max_size"
android:layout_gravity="center_horizontal|bottom"
android:clipChildren="false"
android:clipToPadding="false">
diff --git a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml b/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
deleted file mode 100644
index a3c37e4..0000000
--- a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/dimens.xml
-**
-** Copyright 2013, 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.
-*/
--->
-<resources>
- <!-- Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
- <dimen name="keyguard_security_height">550dp</dimen>
-</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
index 1dc61c5..b7a1bb4 100644
--- a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
@@ -17,10 +17,5 @@
*/
-->
<resources>
-
- <!-- Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
- <dimen name="keyguard_security_height">470dp</dimen>
-
<dimen name="widget_big_font_size">100dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 3861d98..e6593b1 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -29,9 +29,6 @@
(includes 2x keyguard_security_view_top_margin) -->
<dimen name="keyguard_security_height">420dp</dimen>
- <!-- Max Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
-
<!-- pin/password field max height -->
<dimen name="keyguard_password_height">80dp</dimen>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 6e0e38b..88f138f 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -71,8 +71,8 @@
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
android:layout_gravity="center"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
<TextView
android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 891c6af..81ca3718 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -67,8 +67,8 @@
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
android:layout_gravity="center"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
<TextView
android:id="@+id/error"
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 49ef330..fff2544 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -40,6 +40,10 @@
<dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">116dp</dimen>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
<dimen name="global_actions_power_dialog_item_height">130dp</dimen>
<dimen name="global_actions_power_dialog_item_bottom_margin">35dp</dimen>
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index aefd998..a0e721e 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -29,11 +29,11 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">320dp</item>
- <item name="android:maxWidth">320dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:paddingHorizontal">32dp</item>
<item name="android:paddingVertical">20dp</item>
</style>
diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml
index 65ca70b..03365b3 100644
--- a/packages/SystemUI/res/values-sw360dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw360dp/dimens.xml
@@ -25,5 +25,8 @@
<!-- Home Controls -->
<dimen name="global_actions_side_margin">12dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
new file mode 100644
index 0000000..1e26a69
--- /dev/null
+++ b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">248dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 78279ca..96af3c1 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -24,5 +24,8 @@
<!-- Home Controls -->
<dimen name="global_actions_side_margin">16dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
new file mode 100644
index 0000000..c4d9b9b
--- /dev/null
+++ b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index 7da47e5..ff6e005 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -27,4 +27,6 @@
<dimen name="global_actions_grid_item_side_margin">12dp</dimen>
<dimen name="global_actions_grid_item_height">72dp</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 8148d3d..5ca2b43 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -16,16 +16,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">120dp</item>
- <item name="android:paddingVertical">40dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 771de08..41d931d 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -24,16 +24,6 @@
<item name="android:layout_gravity">top</item>
</style>
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">180dp</item>
- <item name="android:paddingVertical">80dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 599bf30..9bc0dde 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -92,4 +92,6 @@
<dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
<dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index f9ed67d..d9406d3 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -16,16 +16,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">120dp</item>
- <item name="android:paddingVertical">40dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 78d299c..41d931d 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -24,16 +24,6 @@
<item name="android:layout_gravity">top</item>
</style>
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">240dp</item>
- <item name="android:paddingVertical">120dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 0705017..927059a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -22,5 +22,8 @@
<dimen name="controls_padding_horizontal">75dp</dimen>
<dimen name="large_screen_shade_header_height">56dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw800dp/dimens.xml b/packages/SystemUI/res/values-sw800dp/dimens.xml
new file mode 100644
index 0000000..0d82217
--- /dev/null
+++ b/packages/SystemUI/res/values-sw800dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9f29c5b..569b661 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -959,6 +959,10 @@
<!-- Biometric Auth Credential values -->
<dimen name="biometric_auth_icon_size">48dp</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
<!-- Starting text size in sp of batteryLevel for wireless charging animation -->
<item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
0
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 0c91ced..aafa47f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -251,11 +251,12 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:padding">20dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:paddingHorizontal">32dp</item>
+ <item name="android:paddingVertical">20dp</item>
</style>
<style name="AuthCredentialPinPasswordContainerStyle">
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
index 49cc483..e032bb9 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -34,13 +34,19 @@
/**
* A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
*/
-class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ExternalViewScreenshotTestRule(
+ emulationSpec: DeviceEmulationSpec,
+ assetPathRelativeToBuildRoot: String
+) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ SystemUIGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetPathRelativeToBuildRoot
+ )
)
private val delegateRule =
RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
index fafc774..72d8c5a 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
@@ -23,11 +23,11 @@
/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
class SystemUIGoldenImagePathManager(
pathConfig: PathConfig,
- override val assetsPathRelativeToRepo: String = "tests/screenshot/assets"
+ assetsPathRelativeToBuildRoot: String
) :
GoldenImagePathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
- assetsPathRelativeToRepo = assetsPathRelativeToRepo,
+ assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
InstrumentationRegistry.getInstrumentation()
.targetContext
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 36ac1ff..6d0cc5e 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -44,17 +44,16 @@
emulationSpec: DeviceEmulationSpec,
private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
- assetsPathRelativeToRepo: String = ""
+ assetsPathRelativeToBuildRoot: String
) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- if (assetsPathRelativeToRepo.isBlank()) {
- SystemUIGoldenImagePathManager(pathConfig)
- } else {
- SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo)
- }
+ SystemUIGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetsPathRelativeToBuildRoot
+ )
)
private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
private val delegateRule =
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 8197685..e6283b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -26,7 +26,6 @@
val credentialAttempted: Boolean,
val deviceInteractive: Boolean,
val dreaming: Boolean,
- val encryptedOrLockdown: Boolean,
val fingerprintDisabled: Boolean,
val fingerprintLockedOut: Boolean,
val goingToSleep: Boolean,
@@ -37,6 +36,7 @@
val primaryUser: Boolean,
val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
+ val strongerAuthRequired: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
val userDoesNotHaveTrust: Boolean
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 01be33e..4d0a273 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -363,16 +363,18 @@
final boolean sfpsEnabled = getResources().getBoolean(
R.bool.config_show_sidefps_hint_on_bouncer);
final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
- final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth();
+ final boolean isUnlockingWithFpAllowed =
+ mUpdateMonitor.isUnlockingWithFingerprintAllowed();
- boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth;
+ boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning
+ && isUnlockingWithFpAllowed;
if (DEBUG) {
Log.d(TAG, "sideFpsToShow=" + toShow + ", "
+ "mBouncerVisible=" + mBouncerVisible + ", "
+ "configEnabled=" + sfpsEnabled + ", "
+ "fpsDetectionRunning=" + fpsDetectionRunning + ", "
- + "needsStrongAuth=" + needsStrongAuth);
+ + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed);
}
if (toShow) {
mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 39ade34..993d80f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -27,6 +27,8 @@
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
import static android.hardware.biometrics.BiometricConstants.LockoutMode;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -228,7 +230,15 @@
* Biometric authentication: Cancelling and waiting for the relevant biometric service to
* send us the confirmation that cancellation has happened.
*/
- private static final int BIOMETRIC_STATE_CANCELLING = 2;
+ @VisibleForTesting
+ protected static final int BIOMETRIC_STATE_CANCELLING = 2;
+
+ /**
+ * Biometric state: During cancelling we got another request to start listening, so when we
+ * receive the cancellation done signal, we should start listening again.
+ */
+ @VisibleForTesting
+ protected static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
/**
* Action indicating keyguard *can* start biometric authentiation.
@@ -243,12 +253,6 @@
*/
private static final int BIOMETRIC_ACTION_UPDATE = 2;
- /**
- * Biometric state: During cancelling we got another request to start listening, so when we
- * receive the cancellation done signal, we should start listening again.
- */
- private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
-
@VisibleForTesting
public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
@@ -356,7 +360,8 @@
private KeyguardBypassController mKeyguardBypassController;
private List<SubscriptionInfo> mSubscriptionInfo;
- private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ @VisibleForTesting
+ protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
@@ -790,7 +795,7 @@
new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
- mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
+ mTrustManager.unlockedByBiometricForUser(userId, FINGERPRINT);
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
@@ -800,7 +805,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+ cb.onBiometricAuthenticated(userId, FINGERPRINT,
isStrongBiometric);
}
}
@@ -833,7 +838,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ cb.onBiometricAuthFailed(FINGERPRINT);
}
}
if (isUdfpsSupported()) {
@@ -858,7 +863,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT, acquireInfo);
+ cb.onBiometricAcquired(FINGERPRINT, acquireInfo);
}
}
}
@@ -892,7 +897,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT);
+ cb.onBiometricHelp(msgId, helpString, FINGERPRINT);
}
}
}
@@ -944,7 +949,7 @@
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged = !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
- mLogger.d("Fingerprint locked out - requiring strong auth");
+ mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
mLockPatternUtils.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
}
@@ -953,6 +958,7 @@
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOut;
mFingerprintLockedOut = true;
+ mLogger.d("Fingerprint temporarily locked out - requiring stronger auth");
if (isUdfpsEnrolled()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -963,12 +969,12 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
+ cb.onBiometricError(msgId, errString, FINGERPRINT);
}
}
if (lockedOutStateChanged) {
- notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ notifyLockedOutStateChanged(FINGERPRINT);
}
}
@@ -996,7 +1002,7 @@
}
if (changed) {
- notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ notifyLockedOutStateChanged(FINGERPRINT);
}
}
@@ -1019,7 +1025,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(),
- BiometricSourceType.FINGERPRINT);
+ FINGERPRINT);
}
}
}
@@ -1032,7 +1038,7 @@
new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
- mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
+ mTrustManager.unlockedByBiometricForUser(userId, FACE);
}
// Don't send cancel if authentication succeeds
mFaceCancelSignal = null;
@@ -1043,7 +1049,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricAuthenticated(userId,
- BiometricSourceType.FACE,
+ FACE,
isStrongBiometric);
}
}
@@ -1065,7 +1071,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthFailed(BiometricSourceType.FACE);
+ cb.onBiometricAuthFailed(FACE);
}
}
handleFaceHelp(BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
@@ -1078,7 +1084,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+ cb.onBiometricAcquired(FACE, acquireInfo);
}
}
}
@@ -1113,7 +1119,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE);
+ cb.onBiometricHelp(msgId, helpString, FACE);
}
}
}
@@ -1181,12 +1187,12 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricError(msgId, errString,
- BiometricSourceType.FACE);
+ FACE);
}
}
if (lockedOutStateChanged) {
- notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ notifyLockedOutStateChanged(FACE);
}
}
@@ -1200,7 +1206,7 @@
FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
if (changed) {
- notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ notifyLockedOutStateChanged(FACE);
}
}
@@ -1223,7 +1229,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricRunningStateChanged(isFaceDetectionRunning(),
- BiometricSourceType.FACE);
+ FACE);
}
}
}
@@ -1364,7 +1370,39 @@
}
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
- return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
+ // StrongAuthTracker#isUnlockingWithBiometricAllowed includes
+ // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
+ // however the strong auth tracker does not include the temporary lockout
+ // mFingerprintLockedOut.
+ return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
+ && !mFingerprintLockedOut;
+ }
+
+ private boolean isUnlockingWithFaceAllowed() {
+ return mStrongAuthTracker.isUnlockingWithBiometricAllowed(false);
+ }
+
+ /**
+ * Whether fingerprint is allowed ot be used for unlocking based on the strongAuthTracker
+ * and temporary lockout state (tracked by FingerprintManager via error codes).
+ */
+ public boolean isUnlockingWithFingerprintAllowed() {
+ return isUnlockingWithBiometricAllowed(true);
+ }
+
+ /**
+ * Whether the given biometric is allowed based on strongAuth & lockout states.
+ */
+ public boolean isUnlockingWithBiometricAllowed(
+ @NonNull BiometricSourceType biometricSourceType) {
+ switch (biometricSourceType) {
+ case FINGERPRINT:
+ return isUnlockingWithFingerprintAllowed();
+ case FACE:
+ return isUnlockingWithFaceAllowed();
+ default:
+ return false;
+ }
}
public boolean isUserInLockdown(int userId) {
@@ -1386,11 +1424,6 @@
return isEncrypted || isLockDown;
}
- public boolean userNeedsStrongAuth() {
- return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())
- != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
- }
-
private boolean containsFlag(int haystack, int needle) {
return (haystack & needle) != 0;
}
@@ -1560,12 +1593,6 @@
}
};
- private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback
- = (sensorId, userId, isStrongBiometric) -> {
- // Trigger the fingerprint success path so the bouncer can be shown
- handleFingerprintAuthenticated(userId, isStrongBiometric);
- };
-
/**
* Propagates a pointer down event to keyguard.
*/
@@ -2636,27 +2663,25 @@
&& (!mKeyguardGoingAway || !mDeviceInteractive)
&& mIsPrimaryUser
&& biometricEnabledForUser;
-
- final boolean shouldListenBouncerState = !(mFingerprintLockedOut
- && mPrimaryBouncerIsOrWillBeShowing && mCredentialAttempted);
-
- final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+ final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+ final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
+ final boolean shouldListenBouncerState =
+ !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
+ && !strongerAuthRequired
&& userDoesNotHaveTrust);
boolean shouldListenSideFpsState = true;
- if (isSfpsSupported() && isSfpsEnrolled()) {
+ if (isSideFps) {
shouldListenSideFpsState =
mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
}
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenBouncerState && shouldListenUdfpsState
&& shouldListenSideFpsState;
-
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
System.currentTimeMillis(),
@@ -2668,7 +2693,6 @@
mCredentialAttempted,
mDeviceInteractive,
mIsDreaming,
- isEncryptedOrLockdownForUser,
fingerprintDisabledForUser,
mFingerprintLockedOut,
mGoingToSleep,
@@ -2679,6 +2703,7 @@
mIsPrimaryUser,
shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
+ strongerAuthRequired,
mSwitchingUser,
isUdfps,
userDoesNotHaveTrust));
@@ -2706,10 +2731,7 @@
final boolean isEncryptedOrTimedOut =
containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
-
- // TODO: always disallow when fp is already locked out?
- final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
-
+ final boolean fpLockedOut = isFingerprintLockedOut();
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
@@ -2831,15 +2853,22 @@
// Waiting for restart via handleFingerprintError().
return;
}
- mLogger.v("startListeningForFingerprint()");
if (unlockPossible) {
mFingerprintCancelSignal = new CancellationSignal();
- if (isEncryptedOrLockdown(userId)) {
- mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
+ if (!isUnlockingWithFingerprintAllowed()) {
+ mLogger.v("startListeningForFingerprint - detect");
+ mFpm.detectFingerprint(
+ mFingerprintCancelSignal,
+ (sensorId, user, isStrongBiometric) -> {
+ mLogger.d("fingerprint detected");
+ // Trigger the fingerprint success path so the bouncer can be shown
+ handleFingerprintAuthenticated(user, isStrongBiometric);
+ },
userId);
} else {
+ mLogger.v("startListeningForFingerprint - authenticate");
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback, null /* handler */,
FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */);
@@ -3056,11 +3085,15 @@
}
}
+ // Immediately stop previous biometric listening states.
+ // Resetting lockout states updates the biometric listening states.
if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+ stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
mFaceSensorProperties.get(0).sensorId, userId));
}
if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+ stopListeningForFingerprint();
handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
mFingerprintSensorProperties.get(0).sensorId, userId));
}
@@ -3467,7 +3500,7 @@
@AnyThread
public void setSwitchingUser(boolean switching) {
mSwitchingUser = switching;
- // Since this comes in on a binder thread, we need to post if first
+ // Since this comes in on a binder thread, we need to post it first
mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_USER_SWITCHING));
}
@@ -3559,8 +3592,8 @@
Assert.isMainThread();
mUserFingerprintAuthenticated.clear();
mUserFaceAuthenticated.clear();
- mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser);
- mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser);
+ mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
+ mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
mLogger.d("clearBiometricRecognized");
for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index fc5f447..6ac54fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -116,9 +116,9 @@
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
+ fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
if (!keyguardStateController.isShowing ||
- keyguardUpdateMonitor.userNeedsStrongAuth()) {
+ !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(biometricSourceType)) {
return
}
@@ -246,7 +246,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated(
userId: Int,
- biometricSourceType: BiometricSourceType?,
+ biometricSourceType: BiometricSourceType,
isStrongBiometric: Boolean
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
@@ -255,14 +255,14 @@
showUnlockRipple(biometricSourceType)
}
- override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+ override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
mView.retractDwellRipple()
}
}
override fun onBiometricAcquired(
- biometricSourceType: BiometricSourceType?,
+ biometricSourceType: BiometricSourceType,
acquireInfo: Int
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 84a8074..2cf5fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.res.ColorStateList
+import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.Trace
import android.os.UserHandle
@@ -71,7 +72,7 @@
KeyguardUpdateMonitor.getCurrentUser()
) &&
!needsFullscreenBouncer() &&
- !keyguardUpdateMonitor.userNeedsStrongAuth() &&
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
!keyguardBypassController.bypassEnabled
/** Runnable to show the primary bouncer. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a3d9965..7fbdeca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2072,13 +2072,6 @@
mInitialTouchX = x;
initVelocityTracker();
trackMovement(event);
- float qsExpansionFraction = computeQsExpansionFraction();
- // Intercept the touch if QS is between fully collapsed and fully expanded state
- if (qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) {
- mShadeLog.logMotionEvent(event,
- "onQsIntercept: down action, QS partially expanded/collapsed");
- return true;
- }
if (mKeyguardShowing
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
// Dragging down on the lockscreen statusbar should prohibit other interactions
@@ -2331,13 +2324,6 @@
if (!isFullyCollapsed()) {
handleQsDown(event);
}
- // defer touches on QQS to shade while shade is collapsing. Added margin for error
- // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
- if (computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
- mShadeLog.logMotionEvent(event,
- "handleQsTouch: QQS touched while shade collapsing");
- mQsTracking = false;
- }
if (!mQsExpandImmediate && mQsTracking) {
onQsTouch(event);
if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
@@ -2578,6 +2564,7 @@
// Reset scroll position and apply that position to the expanded height.
float height = mQsExpansionHeight;
setQsExpansionHeight(height);
+ updateExpandedHeightToMaxHeight();
mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
// When expanding QS, let's authenticate the user if possible,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index aa0757e..000fe14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -240,8 +240,8 @@
&& !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
KeyguardUpdateMonitor.getCurrentUser())
&& !needsFullscreenBouncer()
- && !mKeyguardUpdateMonitor.isFaceLockedOut()
- && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+ && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE)
&& !mKeyguardBypassController.getBypassEnabled()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 8839662..afd582a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -63,7 +63,6 @@
credentialAttempted = false,
deviceInteractive = false,
dreaming = false,
- encryptedOrLockdown = false,
fingerprintDisabled = false,
fingerprintLockedOut = false,
goingToSleep = false,
@@ -74,6 +73,7 @@
primaryUser = false,
shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
+ strongerAuthRequired = false,
switchingUser = false,
udfps = false,
userDoesNotHaveTrust = false
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 4d58b09..e39b9b5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -379,9 +379,9 @@
}
@Test
- public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
+ public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
- setNeedsStrongAuth(true);
+ setUnlockingWithFingerprintAllowed(false);
reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -574,7 +574,7 @@
attachView();
setSideFpsHintEnabledFromResources(true);
setFingerprintDetectionRunning(true);
- setNeedsStrongAuth(false);
+ setUnlockingWithFingerprintAllowed(true);
}
private void attachView() {
@@ -593,9 +593,8 @@
enabled);
}
- private void setNeedsStrongAuth(boolean needed) {
- when(mKeyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(needed);
- mKeyguardUpdateMonitorCallback.getValue().onStrongAuthStateChanged(/* userId= */ 0);
+ private void setUnlockingWithFingerprintAllowed(boolean allowed) {
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed);
}
private void setupGetSecurityView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7231b34..63e1603 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -28,6 +28,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
@@ -281,7 +282,6 @@
componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
false /* resetLockoutRequiresChallenge */));
-
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
@@ -594,30 +594,13 @@
}
@Test
- public void testFingerprintDoesNotAuth_whenEncrypted() {
- testFingerprintWhenStrongAuth(
- STRONG_AUTH_REQUIRED_AFTER_BOOT);
- }
-
- @Test
- public void testFingerprintDoesNotAuth_whenDpmLocked() {
- testFingerprintWhenStrongAuth(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
- }
-
- @Test
- public void testFingerprintDoesNotAuth_whenUserLockdown() {
- testFingerprintWhenStrongAuth(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- }
-
- private void testFingerprintWhenStrongAuth(int strongAuth) {
+ public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
// Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
// will trigger updateBiometricListeningState();
clearInvocations(mFingerprintManager);
mKeyguardUpdateMonitor.resetBiometricListeningState();
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
@@ -928,10 +911,6 @@
faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
final boolean fpLocked =
fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
- when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
- .thenReturn(fingerprintLockoutMode);
- when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
- .thenReturn(faceLockoutMode);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
@@ -940,7 +919,13 @@
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
anyInt());
+// resetFaceManager();
+// resetFingerprintManager();
+ when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
+ .thenReturn(fingerprintLockoutMode);
+ when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
+ .thenReturn(faceLockoutMode);
final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
@@ -951,14 +936,22 @@
mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
mTestableLooper.processAllMessages();
- verify(faceCancel, faceLocked ? times(1) : never()).cancel();
- verify(fpCancel, fpLocked ? times(1) : never()).cancel();
- verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged(
+ // THEN face and fingerprint listening are always cancelled immediately
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
eq(false), eq(BiometricSourceType.FACE));
- verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged(
+ verify(fpCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
eq(false), eq(BiometricSourceType.FINGERPRINT));
+
+ // THEN locked out states are updated
assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+
+ // Fingerprint should be restarted once its cancelled bc on lockout, the device
+ // can still detectFingerprint (and if it's not locked out, fingerprint can listen)
+ assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+ .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
}
@Test
@@ -1144,9 +1137,8 @@
// GIVEN status bar state is on the keyguard
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
- // WHEN user hasn't authenticated since last boot
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
- .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ // WHEN user hasn't authenticated since last boot, cannot unlock with FP
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1258,8 +1250,7 @@
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// WHEN device in lock down
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 0b528a5..eb8c823 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -37,7 +37,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.leak.RotationUtils
-import javax.inject.Provider
+import com.android.systemui.util.mockito.any
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -46,15 +46,16 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
+import javax.inject.Provider
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -118,12 +119,13 @@
@Test
fun testFingerprintTrigger_KeyguardShowing_Ripple() {
- // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
+ // GIVEN fp exists, keyguard is showing, unlocking with fp allowed
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
// WHEN fingerprint authenticated
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -140,11 +142,12 @@
@Test
fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
- // GIVEN fp exists & user doesn't need strong auth
+ // GIVEN fp exists & unlocking with fp allowed
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
// WHEN keyguard is NOT showing & fingerprint authenticated
`when`(keyguardStateController.isShowing).thenReturn(false)
@@ -160,15 +163,16 @@
}
@Test
- fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
+ fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() {
// GIVEN fp exists & keyguard is showing
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- // WHEN user needs strong auth & fingerprint authenticated
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
+ // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
@@ -182,13 +186,14 @@
@Test
fun testFaceTriggerBypassEnabled_Ripple() {
- // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
+ // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE)).thenReturn(true)
// WHEN bypass is enabled & face authenticated
`when`(bypassController.canBypass()).thenReturn(true)
@@ -275,6 +280,8 @@
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT)).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -295,6 +302,8 @@
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
`when`(authController.isUdfpsFingerDown).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FACE))).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FACE)
assertTrue("reveal didn't start on keyguardFadingAway",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index d3b5418..df7ee43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -39,6 +39,7 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -398,6 +399,8 @@
@Test
public void testShow_delaysIfFaceAuthIsRunning() {
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(true);
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
mBouncer.show(true /* reset */);
@@ -410,9 +413,10 @@
}
@Test
- public void testShow_doesNotDelaysIfFaceAuthIsLockedOut() {
+ public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() {
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(false);
mBouncer.show(true /* reset */);
verify(mHandler, never()).postDelayed(any(), anyLong());
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 87d1668..630cd350 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -860,7 +860,8 @@
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
return IntPair.of(
- getClientStateLocked(userState),
+ combineUserStateAndProxyState(getClientStateLocked(userState),
+ mProxyManager.getStateLocked()),
client.mLastSentRelevantEventTypes);
} else {
userState.mUserClients.register(callback, client);
@@ -872,7 +873,9 @@
+ " and userId:" + mCurrentUserId);
}
return IntPair.of(
- (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+ (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
+ getClientStateLocked(userState), mProxyManager.getStateLocked())
+ : 0,
client.mLastSentRelevantEventTypes);
}
}
@@ -1003,6 +1006,7 @@
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
mUiAutomationManager.sendAccessibilityEventLocked(event);
+ mProxyManager.sendAccessibilityEvent(event);
}
private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
@@ -1143,9 +1147,9 @@
}
List<AccessibilityServiceConnection> services =
getUserStateLocked(resolvedUserId).mBoundServices;
- int numServices = services.size();
+ int numServices = services.size() + mProxyManager.getNumProxys();
interfacesToInterrupt = new ArrayList<>(numServices);
- for (int i = 0; i < numServices; i++) {
+ for (int i = 0; i < services.size(); i++) {
AccessibilityServiceConnection service = services.get(i);
IBinder a11yServiceBinder = service.mService;
IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
@@ -1153,6 +1157,7 @@
interfacesToInterrupt.add(a11yServiceInterface);
}
}
+ mProxyManager.addServiceInterfaces(interfacesToInterrupt);
}
for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
try {
@@ -1941,6 +1946,7 @@
mUiAutomationManager.getServiceInfo(), client)
? mUiAutomationManager.getRelevantEventTypes()
: 0;
+ relevantEventTypes |= mProxyManager.getRelevantEventTypes();
return relevantEventTypes;
}
@@ -2178,21 +2184,25 @@
updateAccessibilityEnabledSettingLocked(userState);
}
- void scheduleUpdateClientsIfNeeded(AccessibilityUserState userState) {
- synchronized (mLock) {
- scheduleUpdateClientsIfNeededLocked(userState);
- }
+ private int combineUserStateAndProxyState(int userState, int proxyState) {
+ return userState | proxyState;
}
void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
final int clientState = getClientStateLocked(userState);
- if (userState.getLastSentClientStateLocked() != clientState
+ final int proxyState = mProxyManager.getStateLocked();
+ if ((userState.getLastSentClientStateLocked() != clientState
+ || mProxyManager.getLastSentStateLocked() != proxyState)
&& (mGlobalClients.getRegisteredCallbackCount() > 0
- || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
+ || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
userState.setLastSentClientStateLocked(clientState);
+ mProxyManager.setLastStateLocked(proxyState);
+ // Send both the user and proxy state to the app for now.
+ // TODO(b/250929565): Send proxy state to proxy clients
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendStateToAllClients,
- this, clientState, userState.mUserId));
+ this, combineUserStateAndProxyState(clientState, proxyState),
+ userState.mUserId));
}
}
@@ -2434,7 +2444,8 @@
// binding we do an update pass after each bind event, so we run this
// code and register the callback if needed.
- boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked();
+ boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked()
+ || mProxyManager.canRetrieveInteractiveWindowsLocked();
List<AccessibilityServiceConnection> boundServices = userState.mBoundServices;
final int boundServiceCount = boundServices.size();
for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
@@ -3657,7 +3668,9 @@
+ "proxy-ed");
}
- mProxyManager.registerProxy(client, displayId);
+ mProxyManager.registerProxy(client, displayId, mContext,
+ sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
+ mWindowManagerService, mA11yWindowManager);
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index e5e1d02..ce269f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -696,7 +696,7 @@
final ResolveInfo resolveInfo = service.getServiceInfo().getResolveInfo();
if (resolveInfo == null) {
- // For InteractionBridge and UiAutomation
+ // For InteractionBridge, UiAutomation, and Proxy.
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 247f320..d7f9c12 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -16,8 +16,12 @@
package com.android.server.accessibility;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityTrace;
+import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.content.ComponentName;
@@ -31,6 +35,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
+import android.os.RemoteException;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityDisplayProxy;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -53,10 +58,6 @@
* TODO(241429275): Initialize this when a proxy is registered.
*/
public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
- // Names used to populate ComponentName and ResolveInfo
- private static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
- private static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
-
private int mDisplayId;
private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
@@ -76,6 +77,16 @@
}
/**
+ * Called when the proxy is registered.
+ */
+ void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
+ throws RemoteException {
+ mServiceInterface = serviceInterface;
+ mService = serviceInterface.asBinder();
+ mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+ }
+
+ /**
* Keeps mAccessibilityServiceInfo in sync with the proxy's list of AccessibilityServiceInfos.
*
* <p>This also sets the properties that are assumed to be populated by installed packages.
@@ -89,7 +100,7 @@
synchronized (mLock) {
mInstalledAndEnabledServices = infos;
final AccessibilityServiceInfo proxyInfo = mAccessibilityServiceInfo;
- // Reset values.
+ // Reset values. mAccessibilityServiceInfo is not completely reset since it is final
proxyInfo.flags = 0;
proxyInfo.eventTypes = 0;
proxyInfo.notificationTimeout = 0;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index a2ce610..2184878 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,9 +14,21 @@
* limitations under the License.
*/
package com.android.server.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
-import java.util.HashSet;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.List;
/**
* Manages proxy connections.
@@ -27,32 +39,185 @@
* TODO(241117292): Remove or cut down during simultaneous user refactoring.
*/
public class ProxyManager {
+ // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
+ // the infos of connection.setInstalledAndEnabledServices
+ static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
+ static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
+
private final Object mLock;
- private final HashSet<Integer> mDisplayIds = new HashSet<>();
+
+ // Used to determine if we should notify AccessibilityManager clients of updates.
+ // TODO(254545943): Separate this so each display id has its own state. Currently there is no
+ // way to identify from AccessibilityManager which proxy state should be returned.
+ private int mLastState = -1;
+
+ private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+ new SparseArray<>();
ProxyManager(Object lock) {
mLock = lock;
}
/**
- * TODO: Create the proxy service connection.
+ * Creates the service connection.
*/
- public void registerProxy(IAccessibilityServiceClient client, int displayId) {
- mDisplayIds.add(displayId);
+ public void registerProxy(IAccessibilityServiceClient client, int displayId,
+ Context context,
+ int id, Handler mainHandler,
+ AccessibilitySecurityPolicy securityPolicy,
+ AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
+ AccessibilityTrace trace,
+ WindowManagerInternal windowManagerInternal,
+ AccessibilityWindowManager awm) throws RemoteException {
+
+ // Set a default AccessibilityServiceInfo that is used before the proxy's info is
+ // populated. A proxy has the touch exploration and window capabilities.
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ | AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
+ final String componentClassDisplayName = PROXY_COMPONENT_CLASS_NAME + displayId;
+ info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME,
+ componentClassDisplayName));
+ ProxyAccessibilityServiceConnection connection =
+ new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
+ id, mainHandler, mLock, securityPolicy, systemSupport, trace,
+ windowManagerInternal,
+ awm, displayId);
+
+ mProxyA11yServiceConnections.put(displayId, connection);
+
+ // If the client dies, make sure to remove the connection.
+ IBinder.DeathRecipient deathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ client.asBinder().unlinkToDeath(this, 0);
+ clearConnection(displayId);
+ }
+ };
+ client.asBinder().linkToDeath(deathRecipient, 0);
+ // Notify apps that the service state has changed.
+ // A11yManager#A11yServicesStateChangeListener
+ connection.mSystemSupport.onClientChangeLocked(true);
+
+ connection.initializeServiceInterface(client);
}
/**
- * TODO: Unregister the proxy service connection based on display id.
+ * Unregister the proxy based on display id.
*/
public boolean unregisterProxy(int displayId) {
- mDisplayIds.remove(displayId);
- return true;
+ return clearConnection(displayId);
+ }
+
+ private boolean clearConnection(int displayId) {
+ if (mProxyA11yServiceConnections.contains(displayId)) {
+ mProxyA11yServiceConnections.remove(displayId);
+ return true;
+ }
+ return false;
}
/**
* Checks if a display id is being proxy-ed.
*/
public boolean isProxyed(int displayId) {
- return mDisplayIds.contains(displayId);
+ return mProxyA11yServiceConnections.contains(displayId);
}
-}
+
+ /**
+ * Sends AccessibilityEvents to all proxies.
+ * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
+ * TODO(b/250929565): Filtering should happen in the system, not in the proxy.
+ */
+ public void sendAccessibilityEvent(AccessibilityEvent event) {
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ proxy.notifyAccessibilityEvent(event);
+ }
+ }
+
+ /**
+ * Returns {@code true} if any proxy can retrieve windows.
+ * TODO(b/250929565): Retrieve per connection/user state.
+ */
+ public boolean canRetrieveInteractiveWindowsLocked() {
+ boolean observingWindows = false;
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy.mRetrieveInteractiveWindows) {
+ observingWindows = true;
+ break;
+ }
+ }
+ return observingWindows;
+ }
+
+ /**
+ * If there is at least one proxy, accessibility is enabled.
+ */
+ public int getStateLocked() {
+ int clientState = 0;
+ final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
+ if (a11yEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+ }
+ return clientState;
+ // TODO(b/254545943): When A11yManager is separated, include support for other properties
+ // like isTouchExplorationEnabled.
+ }
+
+ /**
+ * Gets the last state.
+ */
+ public int getLastSentStateLocked() {
+ return mLastState;
+ }
+
+ /**
+ * Sets the last state.
+ */
+ public void setLastStateLocked(int proxyState) {
+ mLastState = proxyState;
+ }
+
+ /**
+ * Returns the relevant event types of every proxy.
+ * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+ */
+ public int getRelevantEventTypes() {
+ int relevantEventTypes = 0;
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ relevantEventTypes |= proxy.getRelevantEventTypes();
+ }
+ return relevantEventTypes;
+ }
+
+ /**
+ * Gets the number of current proxy connections.
+ * @return
+ */
+ public int getNumProxys() {
+ return mProxyA11yServiceConnections.size();
+ }
+
+ /**
+ * Adds the service interfaces to a list.
+ * @param interfaces
+ */
+ public void addServiceInterfaces(List<IAccessibilityServiceClient> interfaces) {
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ final IBinder proxyBinder = proxy.mService;
+ final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+ if ((proxyBinder != null) && (proxyInterface != null)) {
+ interfaces.add(proxyInterface);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 544dd4e..68880bd 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -73,6 +73,7 @@
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* @hide
@@ -104,6 +105,9 @@
@VisibleForTesting
static final String BUNDLE_CONTENT_DIGEST = "content-digest";
+ static final String APEX_PRELOAD_LOCATION = "/system/apex/";
+ static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";
+
// used for indicating any type of error during MBA measurement
static final int MBA_STATUS_ERROR = 0;
// used for indicating factory condition preloads
@@ -1110,18 +1114,22 @@
}
}
- // TODO(b/259349011): Need to be more robust against package name mismatch in the filename
+ @NonNull
private String getOriginalApexPreinstalledLocation(String packageName,
String currentInstalledLocation) {
- if (currentInstalledLocation.contains("/decompressed/")) {
- String resultPath = "system/apex" + packageName + ".capex";
- File f = new File(resultPath);
- if (f.exists()) {
- return resultPath;
+ // get a listing of all apex files in /system/apex/
+ Set<String> originalApexs = Stream.of(new File(APEX_PRELOAD_LOCATION).listFiles())
+ .filter(f -> !f.isDirectory())
+ .map(File::getName)
+ .collect(Collectors.toSet());
+
+ for (String originalApex : originalApexs) {
+ if (originalApex.startsWith(packageName)) {
+ return APEX_PRELOAD_LOCATION + originalApex;
}
- return "/system/apex/" + packageName + ".next.capex";
}
- return "/system/apex" + packageName + "apex";
+
+ return APEX_PRELOAD_LOCATION_ERROR;
}
/**
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 104d10d..540ed4c 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -302,6 +302,7 @@
getContext(), soundUri);
if (sfx != null) {
sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+ sfx.preferBuiltinDevice(true);
sfx.play();
}
}
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 02c6ca2..27ee627 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -91,7 +91,7 @@
private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
private static final int DEFAULT_QUOTA_KB = 10 * 1024;
private static final int DEFAULT_QUOTA_PERCENT = 10;
- private static final int DEFAULT_RESERVE_PERCENT = 10;
+ private static final int DEFAULT_RESERVE_PERCENT = 0;
private static final int QUOTA_RESCAN_MILLIS = 5000;
private static final boolean PROFILE_DUMP = false;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 30d4b8b..ca86021c 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -52,8 +52,8 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
import android.telephony.BarringInfo;
+import android.telephony.CallAttributes;
import android.telephony.CallQuality;
-import android.telephony.CallState;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
@@ -82,7 +82,6 @@
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.telephony.emergency.EmergencyNumber;
-import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -350,9 +349,9 @@
private CallQuality[] mCallQuality;
- private ArrayList<List<CallState>> mCallStateLists;
+ private CallAttributes[] mCallAttributes;
- // network type of the call associated with the mCallStateLists and mCallQuality
+ // network type of the call associated with the mCallAttributes and mCallQuality
private int[] mCallNetworkType;
private int[] mSrvccState;
@@ -688,6 +687,7 @@
mCallPreciseDisconnectCause = copyOf(mCallPreciseDisconnectCause, mNumPhones);
mCallQuality = copyOf(mCallQuality, mNumPhones);
mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
+ mCallAttributes = copyOf(mCallAttributes, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
@@ -707,7 +707,6 @@
cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
cutListToSize(mCarrierPrivilegeStates, mNumPhones);
cutListToSize(mCarrierServiceStates, mNumPhones);
- cutListToSize(mCallStateLists, mNumPhones);
return;
}
@@ -731,7 +730,8 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallStateLists.add(i, new ArrayList<>());
+ mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -799,7 +799,7 @@
mCallPreciseDisconnectCause = new int[numPhones];
mCallQuality = new CallQuality[numPhones];
mCallNetworkType = new int[numPhones];
- mCallStateLists = new ArrayList<>();
+ mCallAttributes = new CallAttributes[numPhones];
mPreciseDataConnectionStates = new ArrayList<>();
mCellInfo = new ArrayList<>(numPhones);
mImsReasonInfo = new ArrayList<>();
@@ -837,7 +837,8 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallStateLists.add(i, new ArrayList<>());
+ mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -1335,7 +1336,7 @@
}
if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
+ r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2170,30 +2171,11 @@
}
}
- /**
- * Send a notification to registrants that the precise call state has changed.
- *
- * @param phoneId the phoneId carrying the data connection
- * @param subId the subscriptionId for the data connection
- * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
- * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId()} for
- * ringing, foreground & background calls.
- * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
- * background calls.
- * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
- */
- public void notifyPreciseCallState(int phoneId, int subId,
- @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
- @Annotation.ImsCallServiceType int[] imsServiceTypes,
- @Annotation.ImsCallType int[] imsCallTypes) {
+ public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
+ int foregroundCallState, int backgroundCallState) {
if (!checkNotifyPermission("notifyPreciseCallState()")) {
return;
}
-
- int ringingCallState = callStates[CallState.CALL_CLASSIFICATION_RINGING];
- int foregroundCallState = callStates[CallState.CALL_CLASSIFICATION_FOREGROUND];
- int backgroundCallState = callStates[CallState.CALL_CLASSIFICATION_BACKGROUND];
-
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mRingingCallState[phoneId] = ringingCallState;
@@ -2204,11 +2186,11 @@
backgroundCallState,
DisconnectCause.NOT_VALID,
PreciseDisconnectCause.NOT_VALID);
- boolean notifyCallState = true;
+ boolean notifyCallAttributes = true;
if (mCallQuality == null) {
log("notifyPreciseCallState: mCallQuality is null, "
+ "skipping call attributes");
- notifyCallState = false;
+ notifyCallAttributes = false;
} else {
// If the precise call state is no longer active, reset the call network type
// and call quality.
@@ -2217,54 +2199,8 @@
mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mCallQuality[phoneId] = createCallQuality();
}
- mCallStateLists.get(phoneId).clear();
- if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
- CallQuality callQuality = mCallQuality[phoneId];
- mCallStateLists.get(phoneId).add(
- new CallState.Builder(
- callStates[CallState.CALL_CLASSIFICATION_FOREGROUND])
- .setNetworkType(mCallNetworkType[phoneId])
- .setCallQuality(callQuality)
- .setCallClassification(
- CallState.CALL_CLASSIFICATION_FOREGROUND)
- .setImsCallSessionId(imsCallIds[
- CallState.CALL_CLASSIFICATION_FOREGROUND])
- .setImsCallServiceType(imsServiceTypes[
- CallState.CALL_CLASSIFICATION_FOREGROUND])
- .setImsCallType(imsCallTypes[
- CallState.CALL_CLASSIFICATION_FOREGROUND]).build());
-
- }
- if (backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
- mCallStateLists.get(phoneId).add(
- new CallState.Builder(
- callStates[CallState.CALL_CLASSIFICATION_BACKGROUND])
- .setNetworkType(mCallNetworkType[phoneId])
- .setCallQuality(createCallQuality())
- .setCallClassification(
- CallState.CALL_CLASSIFICATION_BACKGROUND)
- .setImsCallSessionId(imsCallIds[
- CallState.CALL_CLASSIFICATION_BACKGROUND])
- .setImsCallServiceType(imsServiceTypes[
- CallState.CALL_CLASSIFICATION_BACKGROUND])
- .setImsCallType(imsCallTypes[
- CallState.CALL_CLASSIFICATION_BACKGROUND]).build());
- }
- if (ringingCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
- mCallStateLists.get(phoneId).add(
- new CallState.Builder(
- callStates[CallState.CALL_CLASSIFICATION_RINGING])
- .setNetworkType(mCallNetworkType[phoneId])
- .setCallQuality(createCallQuality())
- .setCallClassification(
- CallState.CALL_CLASSIFICATION_RINGING)
- .setImsCallSessionId(imsCallIds[
- CallState.CALL_CLASSIFICATION_RINGING])
- .setImsCallServiceType(imsServiceTypes[
- CallState.CALL_CLASSIFICATION_RINGING])
- .setImsCallType(imsCallTypes[
- CallState.CALL_CLASSIFICATION_RINGING]).build());
- }
+ mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
+ mCallNetworkType[phoneId], mCallQuality[phoneId]);
}
for (Record r : mRecords) {
@@ -2277,11 +2213,11 @@
mRemoveList.add(r.binder);
}
}
- if (notifyCallState && r.matchTelephonyCallbackEvent(
+ if (notifyCallAttributes && r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2579,29 +2515,15 @@
// merge CallQuality with PreciseCallState and network type
mCallQuality[phoneId] = callQuality;
mCallNetworkType[phoneId] = callNetworkType;
- if (mCallStateLists.get(phoneId).size() > 0
- && mCallStateLists.get(phoneId).get(0).getCallState()
- == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
- CallState prev = mCallStateLists.get(phoneId).remove(0);
- mCallStateLists.get(phoneId).add(
- 0, new CallState.Builder(prev.getCallState())
- .setNetworkType(callNetworkType)
- .setCallQuality(callQuality)
- .setCallClassification(prev.getCallClassification())
- .setImsCallSessionId(prev.getImsCallSessionId())
- .setImsCallServiceType(prev.getImsCallServiceType())
- .setImsCallType(prev.getImsCallType()).build());
- } else {
- log("There is no active call to report CallQaulity");
- return;
- }
+ mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
+ callNetworkType, callQuality);
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -3069,6 +2991,7 @@
pw.println("mSrvccState=" + mSrvccState[i]);
pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
pw.println("mCallQuality=" + mCallQuality[i]);
+ pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING
index 0ba4d8c..feb2b4f 100644
--- a/services/core/java/com/android/server/app/TEST_MAPPING
+++ b/services/core/java/com/android/server/app/TEST_MAPPING
@@ -18,6 +18,23 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "FrameworksCoreGameManagerTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.app"
+ }
+ ],
+ "file_patterns": [
+ "(/|^)GameManagerService.java", "(/|^)GameManagerSettings.java"
+ ]
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 1c57151..229393d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -421,13 +421,6 @@
return -1;
}
- if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
- // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
- // ever be invoked when the user is encrypted or lockdown.
- Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return -1;
- }
-
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index b882c47..e16ca0b 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -551,6 +551,15 @@
lensFacing, ignoreResizableAndSdkCheck);
}
+ /**
+ * Placeholder method to fetch the system state for autoframing.
+ * TODO: b/260617354
+ */
+ @Override
+ public int getAutoframingOverride(String packageName) {
+ return CaptureRequest.CONTROL_AUTOFRAMING_OFF;
+ }
+
@Override
public void pingForUserUpdate() {
if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f6b1c9..282ad57 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -81,6 +81,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.provider.Settings;
@@ -930,9 +931,15 @@
}
private void updateEnabled() {
- // Generally follow location setting for current user
- boolean enabled = mContext.getSystemService(LocationManager.class)
- .isLocationEnabledForUser(UserHandle.CURRENT);
+ boolean enabled = false;
+
+ // Generally follow location setting for visible users
+ LocationManager locationManager = mContext.getSystemService(LocationManager.class);
+ Set<UserHandle> visibleUserHandles =
+ mContext.getSystemService(UserManager.class).getVisibleUsers();
+ for (UserHandle visibleUserHandle : visibleUserHandles) {
+ enabled |= locationManager.isLocationEnabledForUser(visibleUserHandle);
+ }
// .. but enable anyway, if there's an active bypass request (e.g. ELS or ADAS)
enabled |= (mProviderRequest != null
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index a94a4e2..ced547c 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -59,6 +59,7 @@
final boolean mForceQueryableOverride;
final int mDataLoaderType;
final int mPackageSource;
+ final boolean mKeepApplicationEnabledSetting;
// The list of instruction sets supported by this app. This is currently
// only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -72,7 +73,8 @@
List<String> allowlistedRestrictedPermissions,
int autoRevokePermissionsMode, String traceMethod, int traceCookie,
SigningDetails signingDetails, int installReason, int installScenario,
- boolean forceQueryableOverride, int dataLoaderType, int packageSource) {
+ boolean forceQueryableOverride, int dataLoaderType, int packageSource,
+ boolean keepApplicationEnabledSetting) {
mOriginInfo = originInfo;
mMoveInfo = moveInfo;
mInstallFlags = installFlags;
@@ -93,6 +95,7 @@
mForceQueryableOverride = forceQueryableOverride;
mDataLoaderType = dataLoaderType;
mPackageSource = packageSource;
+ mKeepApplicationEnabledSetting = keepApplicationEnabledSetting;
}
/**
@@ -104,7 +107,7 @@
null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,
- PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
+ PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, false);
mCodeFile = (codePath != null) ? new File(codePath) : null;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index b02d1a8..78e4190 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_PERMISSION_GROUP;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DEPRECATED_SDK_VERSION;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP;
@@ -136,6 +137,7 @@
import android.os.incremental.IncrementalStorage;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.provider.DeviceConfig;
import android.stats.storage.StorageEnums;
import android.system.ErrnoException;
import android.system.Os;
@@ -1017,6 +1019,28 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ // If the minimum installable SDK version enforcement is enabled, block the install
+ // of apps using a lower target SDK version than required. This helps improve security
+ // and privacy as malware can target older SDK versions to avoid enforcement of new API
+ // behavior.
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__install_block_enabled",
+ false)) {
+ int minInstallableTargetSdk =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__min_installable_target_sdk",
+ 0);
+ if (parsedPackage.getTargetSdkVersion() < minInstallableTargetSdk) {
+ Slog.w(TAG, "App " + parsedPackage.getPackageName()
+ + " targets deprecated sdk version");
+ throw new PrepareFailure(INSTALL_FAILED_DEPRECATED_SDK_VERSION,
+ "App package must target at least version "
+ + minInstallableTargetSdk);
+ }
+ } else {
+ Slog.i(TAG, "Minimum installable target sdk enforcement not enabled");
+ }
+
// Instant apps have several additional install-time checks.
if (instantApp) {
if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) {
@@ -2020,7 +2044,8 @@
Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
}
// Enable system package for requested users
- if (installedForUsers != null) {
+ if (installedForUsers != null
+ && !installRequest.isKeepApplicationEnabledSetting()) {
for (int origUserId : installedForUsers) {
if (userId == UserHandle.USER_ALL || userId == origUserId) {
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
@@ -2070,16 +2095,22 @@
if (userId != UserHandle.USER_ALL) {
// It's implied that when a user requests installation, they want the app to
- // be installed and enabled.
+ // be installed and enabled. The caller, however, can explicitly specify to
+ // keep the existing enabled state.
ps.setInstalled(true, userId);
- ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
+ if (!installRequest.isKeepApplicationEnabledSetting()) {
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
+ installerPackageName);
+ }
} else if (allUsers != null) {
// The caller explicitly specified INSTALL_ALL_USERS flag.
// Thus, updating the settings to install the app for all users.
for (int currentUserId : allUsers) {
ps.setInstalled(true, currentUserId);
- ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
- installerPackageName);
+ if (!installRequest.isKeepApplicationEnabledSetting()) {
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
+ installerPackageName);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 71571dc..5974a9c 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -128,7 +128,8 @@
params.mAutoRevokePermissionsMode,
params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
- params.mDataLoaderType, params.mPackageSource);
+ params.mDataLoaderType, params.mPackageSource,
+ params.mKeepApplicationEnabledSetting);
mPackageMetrics = new PackageMetrics(this);
mIsInstallInherit = params.mIsInherit;
mSessionId = params.mSessionId;
@@ -498,6 +499,10 @@
return mScanResult.mChangedAbiCodePath;
}
+ public boolean isKeepApplicationEnabledSetting() {
+ return mInstallArgs == null ? false : mInstallArgs.mKeepApplicationEnabledSetting;
+ }
+
public boolean isForceQueryableOverride() {
return mInstallArgs != null && mInstallArgs.mForceQueryableOverride;
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 69ced1b..2b6398a 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -98,6 +98,7 @@
final boolean mIsInherit;
final int mSessionId;
final int mRequireUserAction;
+ final boolean mKeepApplicationEnabledSetting;
// For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -130,6 +131,7 @@
mIsInherit = false;
mSessionId = -1;
mRequireUserAction = USER_ACTION_UNSPECIFIED;
+ mKeepApplicationEnabledSetting = false;
}
InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
@@ -163,6 +165,7 @@
mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
mSessionId = sessionId;
mRequireUserAction = sessionParams.requireUserAction;
+ mKeepApplicationEnabledSetting = sessionParams.keepApplicationEnabledSetting;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2ee12bf..3983acf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -269,6 +269,8 @@
private static final String ATTR_SIGNATURE = "signature";
private static final String ATTR_CHECKSUM_KIND = "checksumKind";
private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
+ private static final String ATTR_KEEP_APPLICATION_ENABLED_SETTING =
+ "keepApplicationEnabledSetting";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -1098,6 +1100,7 @@
info.requireUserAction = params.requireUserAction;
info.installerUid = mInstallerUid;
info.packageSource = params.packageSource;
+ info.keepApplicationEnabledSetting = params.keepApplicationEnabledSetting;
}
return info;
}
@@ -4310,6 +4313,11 @@
mPreapprovalRequested.set(true);
}
+ @Override
+ public boolean isKeepApplicationEnabledSetting() {
+ return params.keepApplicationEnabledSetting;
+ }
+
void setSessionReady() {
synchronized (mLock) {
// Do not allow destroyed/failed session to change state
@@ -4691,6 +4699,8 @@
writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
out.attributeInt(null, ATTR_INSTALL_REASON, params.installReason);
+ writeBooleanAttribute(out, ATTR_KEEP_APPLICATION_ENABLED_SETTING,
+ params.keepApplicationEnabledSetting);
final boolean isDataLoader = params.dataLoaderParams != null;
writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader);
@@ -4852,6 +4862,8 @@
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
params.installReason = in.getAttributeInt(null, ATTR_INSTALL_REASON);
params.packageSource = in.getAttributeInt(null, ATTR_PACKAGE_SOURCE);
+ params.keepApplicationEnabledSetting = in.getAttributeBoolean(null,
+ ATTR_KEEP_APPLICATION_ENABLED_SETTING, false);
if (in.getAttributeBoolean(null, ATTR_IS_DATALOADER, false)) {
params.dataLoaderParams = new DataLoaderParams(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index cc1306d..e1efc61 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3234,6 +3234,9 @@
case "--skip-verification":
sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
break;
+ case "--skip-enable":
+ sessionParams.setKeepApplicationEnabledSetting();
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 3dcf926..81f1a98 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static android.os.Process.INVALID_UID;
+
import android.annotation.IntDef;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -209,4 +211,35 @@
deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
!info.mRemovedForAllUsers);
}
+
+ public static void onVerificationFailed(VerifyingSession verifyingSession) {
+ FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+ verifyingSession.getSessionId() /* session_id */,
+ null /* package_name */,
+ INVALID_UID /* uid */,
+ null /* user_ids */,
+ null /* user_types */,
+ null /* original_user_ids */,
+ null /* original_user_types */,
+ verifyingSession.getRet() /* public_return_code */,
+ 0 /* internal_error_code */,
+ 0 /* apks_size_bytes */,
+ 0 /* version_code */,
+ null /* install_steps */,
+ null /* step_duration_millis */,
+ 0 /* total_duration_millis */,
+ 0 /* install_flags */,
+ verifyingSession.getInstallerPackageUid() /* installer_package_uid */,
+ INVALID_UID /* original_installer_package_uid */,
+ verifyingSession.getDataLoaderType() /* data_loader_type */,
+ verifyingSession.getUserActionRequiredType() /* user_action_required_type */,
+ verifyingSession.isInstant() /* is_instant */,
+ false /* is_replace */,
+ false /* is_system */,
+ verifyingSession.isInherit() /* is_inherit */,
+ false /* is_installing_existing_as_user */,
+ false /* is_move_install */,
+ verifyingSession.isStaged() /* is_staged */
+ );
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 88e12fa..23a6b67 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3783,10 +3783,10 @@
private UserInfo getEarliestCreatedFullUser() {
final List<UserInfo> users = getUsersInternal(true, true, true);
UserInfo earliestUser = users.get(0);
- long earliestCreationTime = earliestUser.creationTime;
+ long earliestCreationTime = Long.MAX_VALUE;
for (int i = 0; i < users.size(); i++) {
final UserInfo info = users.get(i);
- if (info.isFull() && info.isAdmin() && info.creationTime > 0
+ if (info.isFull() && info.isAdmin() && info.creationTime >= 0
&& info.creationTime < earliestCreationTime) {
earliestCreationTime = info.creationTime;
earliestUser = info;
diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java
index e1026b4..30f2132 100644
--- a/services/core/java/com/android/server/pm/VerificationUtils.java
+++ b/services/core/java/com/android/server/pm/VerificationUtils.java
@@ -112,7 +112,7 @@
VerificationUtils.broadcastPackageVerified(verificationId, originUri,
verificationCode, null,
- verifyingSession.mDataLoaderType, verifyingSession.getUser(),
+ verifyingSession.getDataLoaderType(), verifyingSession.getUser(),
pms.mContext);
if (state.isInstallAllowed()) {
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 6160519..a54f526 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -19,6 +19,7 @@
import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.EXTRA_VERSION_CODE;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
@@ -114,30 +115,32 @@
final OriginInfo mOriginInfo;
final IPackageInstallObserver2 mObserver;
- final int mInstallFlags;
+ private final int mInstallFlags;
@NonNull
- final InstallSource mInstallSource;
- final String mPackageAbiOverride;
- final VerificationInfo mVerificationInfo;
- final SigningDetails mSigningDetails;
+ private final InstallSource mInstallSource;
+ private final String mPackageAbiOverride;
+ private final VerificationInfo mVerificationInfo;
+ private final SigningDetails mSigningDetails;
@Nullable
MultiPackageVerifyingSession mParentVerifyingSession;
- final long mRequiredInstalledVersionCode;
- final int mDataLoaderType;
- final int mSessionId;
- final boolean mUserActionRequired;
-
+ private final long mRequiredInstalledVersionCode;
+ private final int mDataLoaderType;
+ private final int mSessionId;
+ private final boolean mUserActionRequired;
+ private final int mUserActionRequiredType;
private boolean mWaitForVerificationToComplete;
private boolean mWaitForIntegrityVerificationToComplete;
private boolean mWaitForEnableRollbackToComplete;
private int mRet = PackageManager.INSTALL_SUCCEEDED;
private String mErrorMessage = null;
+ private final boolean mIsInherit;
+ private final boolean mIsStaged;
- final PackageLite mPackageLite;
+ private final PackageLite mPackageLite;
private final UserHandle mUser;
@NonNull
- final PackageManagerService mPm;
- final InstallPackageHelper mInstallPackageHelper;
+ private final PackageManagerService mPm;
+ private final InstallPackageHelper mInstallPackageHelper;
VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
@@ -164,6 +167,9 @@
mSessionId = sessionId;
mPackageLite = lite;
mUserActionRequired = userActionRequired;
+ mUserActionRequiredType = sessionParams.requireUserAction;
+ mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
+ mIsStaged = sessionParams.isStaged;
}
@Override
@@ -186,7 +192,7 @@
// Perform package verification and enable rollback (unless we are simply moving the
// package).
if (!mOriginInfo.mExisting) {
- if ((mInstallFlags & PackageManager.INSTALL_APEX) == 0) {
+ if (!isApex()) {
// TODO(b/182426975): treat APEX as APK when APK verification is concerned
sendApkVerificationRequest(pkgLite);
}
@@ -674,10 +680,9 @@
}
final int installerUid = mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid;
- final int installFlags = mInstallFlags;
// Check if installing from ADB
- if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
+ if ((mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
boolean requestedDisableVerification =
(mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0;
return isAdbVerificationEnabled(pkgInfoLite, userId, requestedDisableVerification);
@@ -685,8 +690,7 @@
// only when not installed from ADB, skip verification for instant apps when
// the installer and verifier are the same.
- if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
- && mPm.mInstantAppInstallerActivity != null) {
+ if (isInstant() && mPm.mInstantAppInstallerActivity != null) {
String installerPackage = mPm.mInstantAppInstallerActivity.packageName;
for (String requiredVerifierPackage : requiredVerifierPackages) {
if (installerPackage.equals(requiredVerifierPackage)) {
@@ -818,6 +822,9 @@
return;
}
sendVerificationCompleteNotification();
+ if (mRet != INSTALL_SUCCEEDED) {
+ PackageMetrics.onVerificationFailed(this);
+ }
}
private void sendVerificationCompleteNotification() {
@@ -865,4 +872,28 @@
public UserHandle getUser() {
return mUser;
}
+ public int getSessionId() {
+ return mSessionId;
+ }
+ public int getDataLoaderType() {
+ return mDataLoaderType;
+ }
+ public int getUserActionRequiredType() {
+ return mUserActionRequiredType;
+ }
+ public boolean isInstant() {
+ return (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ }
+ public boolean isInherit() {
+ return mIsInherit;
+ }
+ public int getInstallerPackageUid() {
+ return mInstallSource.mInstallerPackageUid;
+ }
+ public boolean isApex() {
+ return (mInstallFlags & PackageManager.INSTALL_APEX) != 0;
+ }
+ public boolean isStaged() {
+ return mIsStaged;
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index f8c1c92..10cd5d1 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -91,7 +91,7 @@
deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() {
@Override
public void onFlightComplete() {
- timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+ timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback("onFlightComplete()");
}
});
@@ -402,9 +402,9 @@
* Sends a signal to enable telephony fallback. Provided for command-line access for use
* during tests. This is not exposed as a binder API.
*/
- void enableTelephonyFallback() {
+ void enableTelephonyFallback(@NonNull String reason) {
enforceManageTimeZoneDetectorPermission();
- mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+ mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(reason);
}
/**
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 69274db..ab68e83 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -189,7 +189,7 @@
}
private int runEnableTelephonyFallback() {
- mInterface.enableTelephonyFallback();
+ mInterface.enableTelephonyFallback("Command line");
return 0;
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 5768a6b..37e67c9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -35,13 +35,13 @@
* <p>Devices can have zero, one or two automatic time zone detection algorithms available at any
* point in time.
*
- * <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm
+ * <p>The two automatic detection algorithms supported are "telephony" and "location". Algorithm
* availability and use depends on several factors:
* <ul>
* <li>Telephony is only available on devices with a telephony stack.
- * <li>Geolocation is also optional and configured at image creation time. When enabled on a
- * device, its availability depends on the current user's settings, so switching between users can
- * change the automatic algorithm used by the device.</li>
+ * <li>Location is also optional and configured at image creation time. When enabled on a device,
+ * its availability depends on the current user's settings, so switching between users can change
+ * the automatic detection algorithm used by the device.</li>
* </ul>
*
* <p>If there are no automatic time zone detections algorithms available then the user can usually
@@ -56,14 +56,14 @@
* slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous
* suggestion otherwise it will remain in use.
*
- * <p>Geolocation detection is dependent on the current user and their settings. The device retains
- * at most one geolocation suggestion. Generally, use of a device's location is dependent on the
- * user's "location toggle", but even when that is enabled the user may choose to enable / disable
- * the use of geolocation for device time zone detection. If the current user changes to one that
- * does not have geolocation detection enabled, or the user turns off geolocation detection, then
- * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must
- * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it
- * will remain in use.
+ * <p>Location-based detection is dependent on the current user and their settings. The device
+ * retains at most one geolocation suggestion. Generally, use of a device's location is dependent on
+ * the user's "location toggle", but even when that is enabled the user may choose to enable /
+ * disable the use of location for device time zone detection. If the current user changes to one
+ * that does not have location-based detection enabled, or the user turns off the location-based
+ * detection, then the strategy will be sent an event that clears the latest suggestion. Devices
+ * that lose their location fix must have an empty suggestion submitted in order to "withdraw" their
+ * previous suggestion otherwise it will remain in use.
*
* <p>The strategy uses only one algorithm at a time and does not attempt consensus even when
* more than one is available on a device. This "use only one" behavior is deliberate as different
@@ -72,25 +72,27 @@
* users enter areas without the necessary signals. Ultimately, with no perfect algorithm available,
* the user is left to choose which algorithm works best for their circumstances.
*
- * <p>When geolocation detection is supported and enabled, in certain circumstances, such as during
- * international travel, it makes sense to prioritize speed of detection via telephony (when
- * available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can
- * sometimes be slow to get a location fix and can require network connectivity (which cannot be
- * assumed when users are travelling) for server-assisted location detection or time zone lookup.
- * Therefore, as a restricted form of prioritization between geolocation and telephony algorithms,
- * the strategy provides "telephony fallback" behavior, which can be set to "supported" via device
- * config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in
- * response to signals outside of the scope of this class. Telephony fallback allows the use of
- * telephony suggestions to help with faster detection but only until geolocation detection
- * provides a concrete, "certain" suggestion. After geolocation has made the first certain
- * suggestion, telephony fallback is disabled until the next call to {@link
- * #enableTelephonyTimeZoneFallback()}.
+ * <p>When the location detection algorithm is supported and enabled, in certain circumstances, such
+ * as during international travel, it makes sense to prioritize speed of detection via telephony
+ * (when available) Vs waiting for the location-based detection algorithm to reach certainty.
+ * Location-based detection can sometimes be slow to get a location fix and can require network
+ * connectivity (which cannot be assumed when users are travelling) for server-assisted location
+ * detection or time zone lookup. Therefore, as a restricted form of prioritization between location
+ * and telephony algorithms, the strategy provides "telephony fallback mode" behavior, which can be
+ * set to "supported" via device config. Fallback mode is entered at runtime in response to signals
+ * from outside of the strategy, e.g. from a call to {@link
+ * #enableTelephonyTimeZoneFallback(String)}, or from information in the latest {@link
+ * LocationAlgorithmEvent}. For telephony fallback mode to actually use a telephony suggestion, the
+ * location algorithm <em>must</em> report it is uncertain. Telephony fallback allows the use of
+ * telephony suggestions to help with faster detection but only until the location algorithm
+ * provides a concrete, "certain" suggestion. After the location algorithm has made a certain
+ * suggestion, telephony fallback mode is disabled.
*
* <p>Threading:
*
* <p>Implementations of this class must be thread-safe as calls calls like {@link
* #generateMetricsState()} and {@link #dump(IndentingPrintWriter, String[])} may be called on
- * differents thread concurrently with other operations.
+ * different threads concurrently with other operations.
*
* @hide
*/
@@ -181,11 +183,11 @@
void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
/**
- * Tells the strategy that it can fall back to telephony detection while geolocation detection
- * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can
- * disable it again. See {@link TimeZoneDetectorStrategy} for details.
+ * Tells the strategy that it can fall back to telephony detection while the location detection
+ * algorithm remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)}
+ * can disable it again. See {@link TimeZoneDetectorStrategy} for details.
*/
- void enableTelephonyTimeZoneFallback();
+ void enableTelephonyTimeZoneFallback(@NonNull String reason);
/** Generates a state snapshot for metrics. */
@NonNull
@@ -194,6 +196,6 @@
/** Returns {@code true} if the device supports telephony time zone detection. */
boolean isTelephonyTimeZoneDetectionSupported();
- /** Returns {@code true} if the device supports geolocation time zone detection. */
+ /** Returns {@code true} if the device supports location-based time zone detection. */
boolean isGeoTimeZoneDetectionSupported();
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index fa811ef..e0e3565 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -63,12 +63,8 @@
public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {
/**
- * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings
- * / system properties. It can be faked for testing.
- *
- * <p>Note: Because the settings / system properties-derived values can currently be modified
- * independently and from different threads (and processes!), their use is prone to race
- * conditions.
+ * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that
+ * available from {@link #mServiceConfigAccessor}. It can be faked for testing.
*/
@VisibleForTesting
public interface Environment {
@@ -234,7 +230,7 @@
* allows).
*
* <p>This field is only actually used when telephony time zone fallback is supported, but the
- * value is maintained even when it isn't supported as it can be turned on at any time via
+ * value is maintained even when it isn't supported as support can be turned on at any time via
* server flags. The elapsed realtime when the mode last changed is used to help ordering
* between fallback mode switches and suggestions.
*
@@ -421,10 +417,15 @@
notifyStateChangeListenersAsynchronously();
}
- // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
- // will usually disable telephony fallback mode if it is currently enabled.
- // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback.
- disableTelephonyFallbackIfNeeded();
+ // Manage telephony fallback state.
+ if (event.getAlgorithmStatus().couldEnableTelephonyFallback()) {
+ // An event may trigger entry into telephony fallback mode if the status
+ // indicates the location algorithm cannot work and is likely to stay not working.
+ enableTelephonyTimeZoneFallback("handleLocationAlgorithmEvent(), event=" + event);
+ } else {
+ // A certain suggestion will exit telephony fallback mode.
+ disableTelephonyFallbackIfNeeded();
+ }
// Now perform auto time zone detection. The new event may be used to modify the time zone
// setting.
@@ -497,38 +498,41 @@
}
@Override
- public synchronized void enableTelephonyTimeZoneFallback() {
- // Only do any work if fallback is currently not enabled.
+ public synchronized void enableTelephonyTimeZoneFallback(@NonNull String reason) {
+ // Only do any work to enter fallback mode if fallback is currently not already enabled.
if (!mTelephonyTimeZoneFallbackEnabled.getValue()) {
ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
final boolean fallbackEnabled = true;
mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
- String logMsg = "enableTelephonyTimeZoneFallbackMode: "
- + " currentUserConfig=" + currentUserConfig
- + ", mTelephonyTimeZoneFallbackEnabled="
- + mTelephonyTimeZoneFallbackEnabled;
+ String logMsg = "enableTelephonyTimeZoneFallback: "
+ + " reason=" + reason
+ + ", currentUserConfig=" + currentUserConfig
+ + ", mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled;
logTimeZoneDebugInfo(logMsg);
// mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact.
- // If the latest event contains a "certain" geolocation suggestion, then the telephony
- // fallback value needs to be considered after changing it.
+ // If the latest location algorithm event contains a "certain" geolocation suggestion,
+ // then the telephony fallback mode needs to be (re)considered after changing it.
+ //
// With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
// above, and the fact that geolocation suggestions should never have a time in the
- // future, the following call will be a no-op, and telephony fallback will remain
- // enabled. This comment / call is left as a reminder that it is possible for there to
- // be a current, "certain" geolocation suggestion when this signal arrives and it is
- // intentional that fallback stays enabled in this case. The choice to do this
- // is mostly for symmetry WRT the case where fallback is enabled and an old "certain"
- // geolocation is received; that would also leave telephony fallback enabled.
- // This choice means that telephony fallback will remain enabled until a new "certain"
- // geolocation suggestion is received. If, instead, the next geolocation is "uncertain",
- // then telephony fallback will occur.
+ // future, the following call will usually be a no-op, and telephony fallback mode will
+ // remain enabled. This comment / call is left as a reminder that it is possible in some
+ // cases for there to be a current, "certain" geolocation suggestion when an attempt is
+ // made to enable telephony fallback mode and it is intentional that fallback mode stays
+ // enabled in this case. The choice to do this is mostly for symmetry WRT the case where
+ // fallback is enabled and then an old "certain" geolocation suggestion is received;
+ // that would also leave telephony fallback mode enabled.
+ //
+ // This choice means that telephony fallback mode remains enabled if there is an
+ // existing "certain" suggestion until a new "certain" geolocation suggestion is
+ // received. If, instead, the next geolocation suggestion is "uncertain", then telephony
+ // fallback, i.e. the use of a telephony suggestion, will actually occur.
disableTelephonyFallbackIfNeeded();
if (currentUserConfig.isTelephonyFallbackSupported()) {
- String reason = "enableTelephonyTimeZoneFallbackMode";
doAutoTimeZoneDetection(currentUserConfig, reason);
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 3bb0238..14d6d7b 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -226,9 +227,8 @@
mBackAnimationInProgress = true;
// We don't have an application callback, let's find the destination of the back gesture
- Task finalTask = currentTask;
- prevActivity = currentTask.getActivity(
- (r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
+ // The search logic should align with ActivityClientController#finishActivity
+ prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
// TODO Dialog window does not need to attach on activity, check
// window.mAttrs.type != TYPE_BASE_APPLICATION
if ((window.getParent().getChildCount() > 1
@@ -244,12 +244,14 @@
} else if (currentTask.returnsToHomeRootTask()) {
// Our Task should bring back to home
removedWindowContainer = currentTask;
+ prevTask = currentTask.getDisplayArea().getRootHomeTask();
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
mShowWallpaper = true;
} else if (currentActivity.isRootOfTask()) {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
- prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
+ prevTask = currentTask.mRootWindowContainer.getTask(Task::showToCurrentUser,
+ currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
removedWindowContainer = currentTask;
// If it reaches the top activity, we will check the below task from parent.
// If it's null or multi-window, fallback the type to TYPE_CALLBACK.
@@ -423,6 +425,11 @@
void reset(@NonNull WindowContainer close, @NonNull WindowContainer open) {
clearBackAnimateTarget(null);
+ if (close == null || open == null) {
+ Slog.e(TAG, "reset animation with null target close: "
+ + close + " open: " + open);
+ return;
+ }
if (close.asActivityRecord() != null && open.asActivityRecord() != null
&& (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) {
mSwitchType = ACTIVITY_SWITCH;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 30fcd06..d3b9e10 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -35,6 +35,7 @@
import android.os.ICancellationSignal;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.GetCredentialsRequest;
import android.text.TextUtils;
import android.util.Log;
@@ -199,7 +200,7 @@
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(providerCreateSession -> {
providerCreateSession.getRemoteCredentialService().onCreateCredential(
- (android.service.credentials.CreateCredentialRequest)
+ (BeginCreateCredentialRequest)
providerCreateSession.getProviderRequest(),
/*callback=*/providerCreateSession);
});
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index 4cdc457..d0bc074 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -22,7 +22,7 @@
import android.credentials.Credential;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CredentialProviderService;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.CredentialsResponseContent;
/**
* Helper class for setting up pending intent, and extracting objects from it.
@@ -37,14 +37,15 @@
return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
}
- /** Extracts the {@link CredentialsDisplayContent} object added to the result data. */
- public static CredentialsDisplayContent extractCredentialsDisplayContent(Intent resultData) {
+ /** Extracts the {@link CredentialsResponseContent} object added to the result data. */
+ public static CredentialsResponseContent extractResponseContent(Intent resultData) {
if (resultData == null) {
return null;
}
return resultData.getParcelableExtra(
- CredentialProviderService.EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT,
- CredentialsDisplayContent.class);
+ CredentialProviderService
+ .EXTRA_GET_CREDENTIALS_CONTENT_RESULT,
+ CredentialsResponseContent.class);
}
/** Extracts the {@link CreateCredentialResponse} object added to the result data. */
@@ -53,7 +54,7 @@
return null;
}
return resultData.getParcelableExtra(
- CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESPONSE,
+ CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESULT,
CreateCredentialResponse.class);
}
@@ -63,7 +64,7 @@
return null;
}
return resultData.getParcelableExtra(
- CredentialProviderService.EXTRA_GET_CREDENTIAL,
+ CredentialProviderService.EXTRA_CREDENTIAL_RESULT,
Credential.class);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 6bb8c60..332a75e 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -26,11 +26,12 @@
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.Bundle;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CreateCredentialRequest;
-import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.CreateEntry;
import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
-import android.service.credentials.SaveEntry;
import android.util.Log;
import android.util.Slog;
@@ -44,14 +45,14 @@
* Will likely split this into remote response state and UI state.
*/
public final class ProviderCreateSession extends ProviderSession<
- CreateCredentialRequest, CreateCredentialResponse> {
+ BeginCreateCredentialRequest, BeginCreateCredentialResponse> {
private static final String TAG = "ProviderCreateSession";
// Key to be used as an entry key for a save entry
private static final String SAVE_ENTRY_KEY = "save_entry_key";
@NonNull
- private final Map<String, SaveEntry> mUiSaveEntries = new HashMap<>();
+ private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
/** The complete request to be used in the second round. */
private final CreateCredentialRequest mCompleteRequest;
@@ -62,13 +63,19 @@
CredentialProviderInfo providerInfo,
CreateRequestSession createRequestSession,
RemoteCredentialService remoteCredentialService) {
- CreateCredentialRequest providerRequest =
+ CreateCredentialRequest providerCreateRequest =
createProviderRequest(providerInfo.getCapabilities(),
createRequestSession.mClientRequest,
createRequestSession.mClientCallingPackage);
- if (providerRequest != null) {
+ if (providerCreateRequest != null) {
+ // TODO : Replace with proper splitting of request
+ BeginCreateCredentialRequest providerBeginCreateRequest =
+ new BeginCreateCredentialRequest(
+ providerCreateRequest.getCallingPackage(),
+ providerCreateRequest.getType(),
+ new Bundle());
return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
- remoteCredentialService, providerRequest);
+ remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
}
Log.i(TAG, "Unable to create provider session");
return null;
@@ -87,36 +94,28 @@
return null;
}
- private static CreateCredentialRequest getFirstRoundRequest(CreateCredentialRequest request) {
- // TODO: Replace with first round bundle from request when ready
- return new CreateCredentialRequest(
- request.getCallingPackage(),
- request.getType(),
- new Bundle());
- }
-
private ProviderCreateSession(
@NonNull Context context,
@NonNull CredentialProviderInfo info,
@NonNull ProviderInternalCallback callbacks,
@UserIdInt int userId,
@NonNull RemoteCredentialService remoteCredentialService,
- @NonNull CreateCredentialRequest request) {
- super(context, info, getFirstRoundRequest(request), callbacks, userId,
+ @NonNull BeginCreateCredentialRequest beginCreateRequest,
+ @NonNull CreateCredentialRequest completeCreateRequest) {
+ super(context, info, beginCreateRequest, callbacks, userId,
remoteCredentialService);
- // TODO : Replace with proper splitting of request
- mCompleteRequest = request;
+ mCompleteRequest = completeCreateRequest;
setStatus(Status.PENDING);
}
/** Returns the save entry maintained in state by this provider session. */
- public SaveEntry getUiSaveEntry(String entryId) {
+ public CreateEntry getUiSaveEntry(String entryId) {
return mUiSaveEntries.get(entryId);
}
@Override
public void onProviderResponseSuccess(
- @Nullable CreateCredentialResponse response) {
+ @Nullable BeginCreateCredentialResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
onUpdateResponse(response);
}
@@ -138,7 +137,7 @@
}
}
- private void onUpdateResponse(CreateCredentialResponse response) {
+ private void onUpdateResponse(BeginCreateCredentialResponse response) {
Log.i(TAG, "updateResponse with save entries");
mProviderResponse = response;
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
@@ -152,15 +151,15 @@
Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
return null;
}
- final CreateCredentialResponse response = getProviderResponse();
+ final BeginCreateCredentialResponse response = getProviderResponse();
if (response == null) {
Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
}
- if (response.getSaveEntries() != null) {
+ if (response.getCreateEntries() != null) {
Log.i(TAG, "In prepareUiData save entries not null");
return prepareUiProviderData(
- prepareUiSaveEntries(response.getSaveEntries()),
+ prepareUiSaveEntries(response.getCreateEntries()),
null,
/*isDefaultProvider=*/false);
}
@@ -192,24 +191,25 @@
}
}
- private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+ private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
Log.i(TAG, "in populateUiSaveEntries");
List<Entry> uiSaveEntries = new ArrayList<>();
// Populate the save entries
- for (SaveEntry saveEntry : saveEntries) {
+ for (CreateEntry createEntry : saveEntries) {
String entryId = generateEntryId();
- mUiSaveEntries.put(entryId, saveEntry);
+ mUiSaveEntries.put(entryId, createEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
- uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice(),
- saveEntry.getPendingIntent(), setUpFillInIntent(saveEntry.getPendingIntent())));
+ uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, createEntry.getSlice(),
+ createEntry.getPendingIntent(), setUpFillInIntent(
+ createEntry.getPendingIntent())));
}
return uiSaveEntries;
}
private Intent setUpFillInIntent(PendingIntent pendingIntent) {
Intent intent = pendingIntent.getIntent();
- intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
+ intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
mCompleteRequest);
return intent;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index d63cdeb..6cd011b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -29,7 +29,7 @@
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.CredentialsResponseContent;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
import android.util.Log;
@@ -211,20 +211,20 @@
prepareUiAuthenticationAction(mProviderResponse.getAuthenticationAction()),
/*remoteEntry=*/null);
}
- if (mProviderResponse.getCredentialsDisplayContent() != null) {
- Log.i(TAG, "In prepareUiData displayContent not null");
+ if (mProviderResponse.getCredentialsResponseContent() != null) {
+ Log.i(TAG, "In prepareUiData credentialsResponseContent not null");
return prepareUiProviderData(prepareUiActionEntries(
- mProviderResponse.getCredentialsDisplayContent().getActions()),
- prepareUiCredentialEntries(mProviderResponse.getCredentialsDisplayContent()
+ mProviderResponse.getCredentialsResponseContent().getActions()),
+ prepareUiCredentialEntries(mProviderResponse.getCredentialsResponseContent()
.getCredentialEntries()),
/*authenticationAction=*/null,
prepareUiRemoteEntry(mProviderResponse
- .getCredentialsDisplayContent().getRemoteCredentialEntry()));
+ .getCredentialsResponseContent().getRemoteCredentialEntry()));
}
return null;
}
- private Entry prepareUiRemoteEntry(Action remoteCredentialEntry) {
+ private Entry prepareUiRemoteEntry(CredentialEntry remoteCredentialEntry) {
if (remoteCredentialEntry == null) {
return null;
}
@@ -316,11 +316,11 @@
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse != null) {
if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
- CredentialsDisplayContent content = PendingIntentResultHandler
- .extractCredentialsDisplayContent(providerPendingIntentResponse
+ CredentialsResponseContent content = PendingIntentResultHandler
+ .extractResponseContent(providerPendingIntentResponse
.getResultData());
if (content != null) {
- onUpdateResponse(GetCredentialsResponse.createWithDisplayContent(content));
+ onUpdateResponse(GetCredentialsResponse.createWithResponseContent(content));
return;
}
}
@@ -342,7 +342,7 @@
if (response.getAuthenticationAction() != null) {
Log.i(TAG , "updateResponse with authentication entry");
updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
- } else if (response.getCredentialsDisplayContent() != null) {
+ } else if (response.getCredentialsResponseContent() != null) {
Log.i(TAG , "updateResponse with credentialEntries");
// TODO validate response
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 4a07f0a..ac360bd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -23,7 +23,7 @@
import android.credentials.Credential;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
-import android.service.credentials.Action;
+import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
import android.util.Pair;
@@ -50,7 +50,7 @@
@Nullable protected Credential mFinalCredentialResponse;
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
- @Nullable protected Pair<String, Action> mUiRemoteEntry;
+ @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
/**
* Returns true if the given status reflects that the provider state is ready to be shown
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index c2464b5..e385bcb 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,14 +24,14 @@
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.service.credentials.CreateCredentialRequest;
-import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderException.CredentialProviderError;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
-import android.service.credentials.ICreateCredentialCallback;
+import android.service.credentials.IBeginCreateCredentialCallback;
import android.service.credentials.ICredentialProviderService;
import android.service.credentials.IGetCredentialsCallback;
import android.text.format.DateUtils;
@@ -146,27 +146,27 @@
handleExecutionResponse(result, error, cancellationSink, callback)));
}
- /** Main entry point to be called for executing a createCredential call on the remote
+ /** Main entry point to be called for executing a beginCreateCredential call on the remote
* provider service.
* @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderCreateSession} class that maintains provider state
*/
- public void onCreateCredential(@NonNull CreateCredentialRequest request,
- ProviderCallbacks<CreateCredentialResponse> callback) {
+ public void onCreateCredential(@NonNull BeginCreateCredentialRequest request,
+ ProviderCallbacks<BeginCreateCredentialResponse> callback) {
Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
- AtomicReference<CompletableFuture<CreateCredentialResponse>> futureRef =
+ AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
new AtomicReference<>();
- CompletableFuture<CreateCredentialResponse> connectThenExecute = postAsync(service -> {
- CompletableFuture<CreateCredentialResponse> createCredentialFuture =
+ CompletableFuture<BeginCreateCredentialResponse> connectThenExecute = postAsync(service -> {
+ CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
new CompletableFuture<>();
- ICancellationSignal cancellationSignal = service.onCreateCredential(
- request, new ICreateCredentialCallback.Stub() {
+ ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
+ request, new IBeginCreateCredentialCallback.Stub() {
@Override
- public void onSuccess(CreateCredentialResponse response) {
- Log.i(TAG, "In onSuccess onCreateCredential "
+ public void onSuccess(BeginCreateCredentialResponse response) {
+ Log.i(TAG, "In onSuccess onBeginCreateCredential "
+ "in RemoteCredentialService");
createCredentialFuture.complete(response);
}
@@ -179,7 +179,7 @@
createCredentialFuture.completeExceptionally(
new CredentialProviderException(errorCode, errorMsg));
}});
- CompletableFuture<CreateCredentialResponse> future = futureRef.get();
+ CompletableFuture<BeginCreateCredentialResponse> future = futureRef.get();
if (future != null && future.isCancelled()) {
dispatchCancellationSignal(cancellationSignal);
} else {
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index d477cb6..799a7fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -108,7 +109,7 @@
@Before
public void setUp() {
final InternalResourceService irs = mock(InternalResourceService.class);
- when(irs.isVip(anyInt(), anyString())).thenReturn(false);
+ when(irs.isVip(anyInt(), anyString(), anyLong())).thenReturn(false);
mEconomicPolicy = new MockEconomicPolicy(irs);
}
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 ddfa05c..c46ebf2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -402,6 +402,6 @@
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
pkgInfo.applicationInfo = applicationInfo;
- mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(pkgInfo));
+ mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 0f09252..52a550b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -58,7 +58,6 @@
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityWindowAttributes;
import androidx.test.InstrumentationRegistry;
@@ -106,8 +105,6 @@
LABEL,
DESCRIPTION,
TEST_PENDING_INTENT);
- private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
- new AccessibilityAction(ACTION_ID, LABEL);
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
@@ -282,10 +279,12 @@
@Test
public void testRegisterProxy() throws Exception {
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
- verify(mProxyManager).registerProxy(mMockServiceClient, TEST_DISPLAY);
+ verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
+ eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
+ eq(mA11yms), eq(mA11yms.getTraceManager()),
+ eq(mMockWindowManagerService), eq(mMockA11yWindowManager));
}
-
@SmallTest
@Test
public void testRegisterProxyWithoutPermission() throws Exception {
@@ -296,7 +295,8 @@
Assert.fail();
} catch (SecurityException expected) {
}
- verify(mProxyManager, never()).registerProxy(mMockServiceClient, TEST_DISPLAY);
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ any(), any(), any(), any());
}
@SmallTest
@@ -307,7 +307,8 @@
Assert.fail();
} catch (IllegalArgumentException expected) {
}
- verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.DEFAULT_DISPLAY);
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ any(), any(), any(), any());
}
@SmallTest
@@ -318,7 +319,30 @@
Assert.fail();
} catch (IllegalArgumentException expected) {
}
- verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.INVALID_DISPLAY);
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ any(), any(), any(), any());
+ }
+
+ @SmallTest
+ @Test
+ public void testUnRegisterProxyWithPermission() throws Exception {
+ mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
+ mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+
+ verify(mProxyManager).unregisterProxy(TEST_DISPLAY);
+ }
+
+ @SmallTest
+ @Test
+ public void testUnRegisterProxyWithoutPermission() throws Exception {
+ doThrow(SecurityException.class).when(mMockSecurityPolicy)
+ .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+ try {
+ mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+ Assert.fail();
+ } catch (SecurityException expected) {
+ }
+ verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY);
}
@SmallTest
@@ -417,6 +441,8 @@
@SmallTest
@Test
public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
userState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME);
@@ -432,6 +458,8 @@
@SmallTest
@Test
public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
setupAccessibilityServiceConnection(0);
when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index ccc43f2..a5d7a10 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -144,6 +144,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
Display.STATE_ON), mInvalidBrightnessStrategy);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index bcdc65c..1e72369 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -111,7 +111,7 @@
}
@Override
- public void enableTelephonyTimeZoneFallback() {
+ public void enableTelephonyTimeZoneFallback(String reason) {
throw new UnsupportedOperationException();
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 74efdb5..1c014d1 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -29,6 +29,9 @@
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
@@ -65,6 +68,7 @@
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
import android.os.HandlerThread;
+import android.service.timezone.TimeZoneProviderStatus;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
@@ -1148,7 +1152,7 @@
}
@Test
- public void testTelephonyFallback() {
+ public void testTelephonyFallback_enableTelephonyTimeZoneFallbackCalled() {
ConfigurationInternal config = new ConfigurationInternal.Builder(
CONFIG_AUTO_ENABLED_GEO_ENABLED)
.setTelephonyFallbackSupported(true)
@@ -1178,19 +1182,21 @@
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
// Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent locationAlgorithmEvent =
createCertainLocationAlgorithmEvent("Europe/London");
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
}
@@ -1214,17 +1220,19 @@
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent certainLocationAlgorithmEvent =
createCertainLocationAlgorithmEvent("Europe/Rome");
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
createUncertainLocationAlgorithmEvent();
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1246,19 +1254,21 @@
// Make the geolocation algorithm uncertain.
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
.verifyTelephonyFallbackIsEnabled(true);
}
// Make the geolocation algorithm certain, disabling telephony fallback.
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent locationAlgorithmEvent =
createCertainLocationAlgorithmEvent("Europe/Lisbon");
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
@@ -1267,9 +1277,10 @@
// Demonstrate what happens when geolocation is uncertain when telephony fallback is
// enabled.
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false)
.simulateEnableTelephonyFallback()
@@ -1279,6 +1290,132 @@
}
@Test
+ public void testTelephonyFallback_locationAlgorithmEventSuggestsFallback() {
+ ConfigurationInternal config = new ConfigurationInternal.Builder(
+ CONFIG_AUTO_ENABLED_GEO_ENABLED)
+ .setTelephonyFallbackSupported(true)
+ .build();
+
+ Script script = new Script()
+ .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+ .simulateConfigurationInternalChange(config)
+ .resetConfigurationTracking();
+
+ // Confirm initial state is as expected.
+ script.verifyTelephonyFallbackIsEnabled(true)
+ .verifyTimeZoneNotChanged();
+
+ // Although geolocation detection is enabled, telephony fallback should be used initially
+ // and until a suitable "certain" geolocation suggestion is received.
+ {
+ TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+ SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+ "Europe/Paris");
+ script.simulateIncrementClock()
+ .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+ .verifyTimeZoneChangedAndReset(telephonySuggestion)
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Receiving an "uncertain" geolocation suggestion without a status should have no effect.
+ {
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
+ {
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
+ .verifyTelephonyFallbackIsEnabled(false);
+ }
+
+ // Used to record the last telephony suggestion received, which will be used when fallback
+ // takes place.
+ TelephonyTimeZoneSuggestion lastTelephonySuggestion;
+
+ // Telephony suggestions should now be ignored and geolocation detection is "in control".
+ {
+ TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+ SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+ "Europe/Berlin");
+ script.simulateIncrementClock()
+ .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false);
+ lastTelephonySuggestion = telephonySuggestion;
+ }
+
+ // Geolocation suggestions should continue to be used as normal (previous telephony
+ // suggestions are not used, even when the geolocation suggestion is uncertain).
+ {
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
+ LocationAlgorithmEvent certainLocationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
+ script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
+ .verifyTelephonyFallbackIsEnabled(false);
+
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
+ LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
+ createUncertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false);
+
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
+ LocationAlgorithmEvent certainLocationAlgorithmEvent2 =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
+ script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent2)
+ // No change needed, device will already be set to Europe/Rome.
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false);
+ }
+
+ // Enable telephony fallback via a LocationAlgorithmEvent containing an "uncertain"
+ // suggestion.
+ {
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
+ TimeZoneProviderStatus primaryProviderReportedStatus =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionDependencyStatus(
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_UNKNOWN)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
+ .build();
+ LocationAlgorithmEvent uncertainEventBlockedBySettings =
+ createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus);
+ script.simulateLocationAlgorithmEvent(uncertainEventBlockedBySettings)
+ .verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Make the geolocation algorithm certain, disabling telephony fallback.
+ {
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Lisbon");
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
+ .verifyTelephonyFallbackIsEnabled(false);
+ }
+ }
+
+ @Test
public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() {
ConfigurationInternal config = new ConfigurationInternal.Builder(
CONFIG_AUTO_ENABLED_GEO_ENABLED)
@@ -1297,9 +1434,10 @@
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1307,9 +1445,10 @@
// Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
// to
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1319,16 +1458,18 @@
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent certainEvent =
createCertainLocationAlgorithmEvent("Europe/Rome");
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(certainEvent)
+ script.simulateLocationAlgorithmEvent(certainEvent)
.verifyTimeZoneChangedAndReset(certainEvent)
.verifyTelephonyFallbackIsEnabled(false);
+ // Increment the clock before creating the event: the clock's value is used by the event
+ script.simulateIncrementClock();
LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent();
- script.simulateIncrementClock()
- .simulateLocationAlgorithmEvent(uncertainEvent)
+ script.simulateLocationAlgorithmEvent(uncertainEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1549,9 +1690,16 @@
}
private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() {
+ TimeZoneProviderStatus primaryProviderReportedStatus = null;
+ return createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus);
+ }
+
+ private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent(
+ TimeZoneProviderStatus primaryProviderReportedStatus) {
GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion();
LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
- DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null,
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_UNCERTAIN, primaryProviderReportedStatus,
PROVIDER_STATUS_NOT_PRESENT, null);
LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
event.addDebugInfo("Test uncertain event");
@@ -1744,11 +1892,12 @@
}
/**
- * Simulates the time zone detection strategty receiving a signal that allows it to do
+ * Simulates the time zone detection strategy receiving a signal that allows it to do
* telephony fallback.
*/
Script simulateEnableTelephonyFallback() {
- mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+ mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(
+ "simulateEnableTelephonyFallback()");
return this;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index f3f56e0..dc3515d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -114,6 +114,20 @@
}
@Test
+ public void backTypeBackToHomeDifferentUser() {
+ Task taskA = createTask(mDefaultDisplay);
+ ActivityRecord recordA = createActivityRecord(taskA);
+ Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+ doReturn(false).when(taskA).showToCurrentUser();
+
+ withSystemCallback(createTopTaskWithActivity());
+ BackNavigationInfo backNavigationInfo = startBackNavigation();
+ assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+ }
+
+ @Test
public void backTypeCrossActivityWhenBackToPreviousActivity() {
CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 3b50fa4..52cfe25 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -29,7 +29,7 @@
"android.hardware.usb-V1.1-java",
"android.hardware.usb-V1.2-java",
"android.hardware.usb-V1.3-java",
- "android.hardware.usb-V1-java",
+ "android.hardware.usb-V2-java",
"android.hardware.usb.gadget-V1.0-java",
"android.hardware.usb.gadget-V1.1-java",
"android.hardware.usb.gadget-V1.2-java",
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index f8df6c6..4bb9de5 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -73,6 +73,7 @@
import android.service.usb.UsbPortInfoProto;
import android.service.usb.UsbPortManagerProto;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
@@ -87,6 +88,7 @@
import com.android.server.usb.hal.port.UsbPortHal;
import com.android.server.usb.hal.port.UsbPortHalInstance;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -754,6 +756,31 @@
}
}
+ /**
+ * Sets Compliance Warnings for simulated USB port objects.
+ */
+ public void simulateComplianceWarnings(String portId, String complianceWarningsString,
+ IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ final RawPortInfo portInfo = mSimulatedPorts.get(portId);
+ if (portInfo == null) {
+ pw.println("Simulated port not found");
+ return;
+ }
+
+ IntArray complianceWarnings = new IntArray();
+ for (String s : complianceWarningsString.split("[, ]")) {
+ if (s.length() > 0) {
+ complianceWarnings.add(Integer.parseInt(s));
+ }
+ }
+ pw.println("Simulating Compliance Warnings: portId=" + portId
+ + " Warnings=" + complianceWarningsString);
+ portInfo.complianceWarnings = complianceWarnings.toArray();
+ updatePortsLocked(pw, null);
+ }
+ }
+
public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
synchronized (mLock) {
final RawPortInfo portInfo = mSimulatedPorts.get(portId);
@@ -842,7 +869,10 @@
portInfo.contaminantDetectionStatus,
portInfo.usbDataStatus,
portInfo.powerTransferLimited,
- portInfo.powerBrickConnectionStatus, pw);
+ portInfo.powerBrickConnectionStatus,
+ portInfo.supportsComplianceWarnings,
+ portInfo.complianceWarnings,
+ pw);
}
} else {
for (RawPortInfo currentPortInfo : newPortInfo) {
@@ -857,7 +887,10 @@
currentPortInfo.contaminantDetectionStatus,
currentPortInfo.usbDataStatus,
currentPortInfo.powerTransferLimited,
- currentPortInfo.powerBrickConnectionStatus, pw);
+ currentPortInfo.powerBrickConnectionStatus,
+ currentPortInfo.supportsComplianceWarnings,
+ currentPortInfo.complianceWarnings,
+ pw);
}
}
@@ -880,6 +913,9 @@
handlePortRemovedLocked(portInfo, pw);
break;
}
+ if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_CHANGED) {
+ handlePortComplianceWarningLocked(portInfo, pw);
+ }
}
}
@@ -896,6 +932,8 @@
int usbDataStatus,
boolean powerTransferLimited,
int powerBrickConnectionStatus,
+ boolean supportsComplianceWarnings,
+ @NonNull int[] complianceWarnings,
IndentingPrintWriter pw) {
// Only allow mode switch capability for dual role ports.
// Validate that the current mode matches the supported modes we expect.
@@ -949,13 +987,15 @@
portInfo = new PortInfo(mContext.getSystemService(UsbManager.class),
portId, supportedModes, supportedContaminantProtectionModes,
supportsEnableContaminantPresenceProtection,
- supportsEnableContaminantPresenceDetection);
+ supportsEnableContaminantPresenceDetection,
+ supportsComplianceWarnings);
portInfo.setStatus(currentMode, canChangeMode,
currentPowerRole, canChangePowerRole,
currentDataRole, canChangeDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
contaminantDetectionStatus, usbDataStatus,
- powerTransferLimited, powerBrickConnectionStatus);
+ powerTransferLimited, powerBrickConnectionStatus,
+ complianceWarnings);
mPorts.put(portId, portInfo);
} else {
// Validate that ports aren't changing definition out from under us.
@@ -987,13 +1027,13 @@
+ ", current=" + supportsEnableContaminantPresenceDetection);
}
-
if (portInfo.setStatus(currentMode, canChangeMode,
currentPowerRole, canChangePowerRole,
currentDataRole, canChangeDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
contaminantDetectionStatus, usbDataStatus,
- powerTransferLimited, powerBrickConnectionStatus)) {
+ powerTransferLimited, powerBrickConnectionStatus,
+ complianceWarnings)) {
portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
} else {
portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -1019,6 +1059,11 @@
handlePortLocked(portInfo, pw);
}
+ private void handlePortComplianceWarningLocked(PortInfo portInfo, IndentingPrintWriter pw) {
+ logAndPrint(Log.INFO, pw, "USB port compliance warning changed: " + portInfo);
+ sendComplianceWarningBroadcastLocked(portInfo);
+ }
+
private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
handlePortLocked(portInfo, pw);
@@ -1056,6 +1101,23 @@
Manifest.permission.MANAGE_USB));
}
+ private void sendComplianceWarningBroadcastLocked(PortInfo portInfo) {
+ if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_UNCHANGED) {
+ return;
+ }
+ final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+ intent.addFlags(
+ Intent.FLAG_RECEIVER_FOREGROUND |
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(portInfo.mUsbPort));
+ intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
+
+ // Guard against possible reentrance by posting the broadcast from the handler
+ // instead of from within the critical section.
+ mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ Manifest.permission.MANAGE_USB));
+ }
+
private void enableContaminantDetectionIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) {
if (!mConnected.containsKey(portInfo.mUsbPort.getId())) {
return;
@@ -1180,6 +1242,9 @@
public static final int DISPOSITION_READY = 2;
public static final int DISPOSITION_REMOVED = 3;
+ public static final int COMPLIANCE_WARNING_UNCHANGED = 0;
+ public static final int COMPLIANCE_WARNING_CHANGED = 1;
+
public final UsbPort mUsbPort;
public UsbPortStatus mUsbPortStatus;
public boolean mCanChangeMode;
@@ -1191,15 +1256,29 @@
public long mConnectedAtMillis;
// 0 when port is connected. Else reports the last connected duration
public long mLastConnectDurationMillis;
+ // default initialized to 0 which means no changes reported
+ public int mComplianceWarningChange;
PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes,
int supportedContaminantProtectionModes,
boolean supportsEnableContaminantPresenceDetection,
- boolean supportsEnableContaminantPresenceProtection) {
+ boolean supportsEnableContaminantPresenceProtection,
+ boolean supportsComplianceWarnings) {
mUsbPort = new UsbPort(usbManager, portId, supportedModes,
supportedContaminantProtectionModes,
supportsEnableContaminantPresenceDetection,
- supportsEnableContaminantPresenceProtection);
+ supportsEnableContaminantPresenceProtection,
+ supportsComplianceWarnings);
+ mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED;
+ }
+
+ public boolean complianceWarningsChanged(@NonNull int[] complianceWarnings) {
+ if (Arrays.equals(complianceWarnings, mUsbPortStatus.getComplianceWarnings())) {
+ mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED;
+ return false;
+ }
+ mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED;
+ return true;
}
public boolean setStatus(int currentMode, boolean canChangeMode,
@@ -1221,7 +1300,8 @@
supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
UsbPortStatus.DATA_STATUS_UNKNOWN, false,
- UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN);
+ UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN,
+ new int[] {});
dispositionChanged = true;
}
@@ -1266,7 +1346,8 @@
mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
supportedRoleCombinations, contaminantProtectionStatus,
contaminantDetectionStatus, usbDataStatus,
- powerTransferLimited, powerBrickConnectionStatus);
+ powerTransferLimited, powerBrickConnectionStatus,
+ new int[] {});
dispositionChanged = true;
}
@@ -1281,6 +1362,62 @@
return dispositionChanged;
}
+ public boolean setStatus(int currentMode, boolean canChangeMode,
+ int currentPowerRole, boolean canChangePowerRole,
+ int currentDataRole, boolean canChangeDataRole,
+ int supportedRoleCombinations, int contaminantProtectionStatus,
+ int contaminantDetectionStatus, int usbDataStatus,
+ boolean powerTransferLimited, int powerBrickConnectionStatus,
+ @NonNull int[] complianceWarnings) {
+ boolean dispositionChanged = false;
+
+ mCanChangeMode = canChangeMode;
+ mCanChangePowerRole = canChangePowerRole;
+ mCanChangeDataRole = canChangeDataRole;
+ if (mUsbPortStatus == null
+ || mUsbPortStatus.getCurrentMode() != currentMode
+ || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
+ || mUsbPortStatus.getCurrentDataRole() != currentDataRole
+ || mUsbPortStatus.getSupportedRoleCombinations()
+ != supportedRoleCombinations
+ || mUsbPortStatus.getContaminantProtectionStatus()
+ != contaminantProtectionStatus
+ || mUsbPortStatus.getContaminantDetectionStatus()
+ != contaminantDetectionStatus
+ || mUsbPortStatus.getUsbDataStatus()
+ != usbDataStatus
+ || mUsbPortStatus.isPowerTransferLimited()
+ != powerTransferLimited
+ || mUsbPortStatus.getPowerBrickConnectionStatus()
+ != powerBrickConnectionStatus) {
+ if (mUsbPortStatus == null && complianceWarnings.length > 0) {
+ mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED;
+ }
+ mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+ supportedRoleCombinations, contaminantProtectionStatus,
+ contaminantDetectionStatus, usbDataStatus,
+ powerTransferLimited, powerBrickConnectionStatus,
+ complianceWarnings);
+ dispositionChanged = true;
+ } else if (complianceWarningsChanged(complianceWarnings)) {
+ mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+ supportedRoleCombinations, contaminantProtectionStatus,
+ contaminantDetectionStatus, usbDataStatus,
+ powerTransferLimited, powerBrickConnectionStatus,
+ complianceWarnings);
+ }
+
+ if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
+ mConnectedAtMillis = SystemClock.elapsedRealtime();
+ mLastConnectDurationMillis = 0;
+ } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
+ mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
+ mConnectedAtMillis = 0;
+ }
+
+ return dispositionChanged;
+ }
+
void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
long token = dump.start(idName, id);
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 72f6cc3..d821dee 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -1093,6 +1093,23 @@
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
}
+ } else if ("set-compliance-reasons".equals(args[0]) && args.length == 3) {
+ final String portId = args[1];
+ final String complianceWarnings = args[2];
+ if (mPortManager != null) {
+ mPortManager.simulateComplianceWarnings(portId, complianceWarnings, pw);
+ pw.println();
+ mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
+ "", 0);
+ }
+ } else if ("clear-compliance-reasons".equals(args[0]) && args.length == 2) {
+ final String portId = args[1];
+ if (mPortManager != null) {
+ mPortManager.simulateComplianceWarnings(portId, "", pw);
+ pw.println();
+ mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
+ "", 0);
+ }
} else if ("ports".equals(args[0]) && args.length == 1) {
if (mPortManager != null) {
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
@@ -1142,6 +1159,17 @@
pw.println(" dumpsys usb set-contaminant-status \"matrix\" true");
pw.println(" dumpsys usb set-contaminant-status \"matrix\" false");
pw.println();
+ pw.println("Example simulate compliance warnings:");
+ pw.println(" dumpsys usb add-port \"matrix\" dual");
+ pw.println(" dumpsys usb set-compliance-reasons \"matrix\" <reason-list>");
+ pw.println(" dumpsys usb clear-compliance-reasons \"matrix\"");
+ pw.println("<reason-list> is expected to be formatted as \"1, ..., 4\"");
+ pw.println("with reasons that need to be simulated.");
+ pw.println(" 1: debug accessory");
+ pw.println(" 2: bc12");
+ pw.println(" 3: missing rp");
+ pw.println(" 4: type c");
+ pw.println();
pw.println("Example USB device descriptors:");
pw.println(" dumpsys usb dump-descriptors -dump-short");
pw.println(" dumpsys usb dump-descriptors -dump-tree");
diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
index 128a051..e6a3e53 100644
--- a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
+++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
@@ -40,6 +40,8 @@
public int usbDataStatus;
public boolean powerTransferLimited;
public int powerBrickConnectionStatus;
+ public final boolean supportsComplianceWarnings;
+ public int[] complianceWarnings;
public RawPortInfo(String portId, int supportedModes) {
this.portId = portId;
@@ -50,9 +52,10 @@
this.supportsEnableContaminantPresenceDetection = false;
this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
this.usbDataStatus = UsbPortStatus.DATA_STATUS_UNKNOWN;
-
this.powerTransferLimited = false;
this.powerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
+ this.supportsComplianceWarnings = false;
+ this.complianceWarnings = new int[] {};
}
public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
@@ -66,6 +69,29 @@
int usbDataStatus,
boolean powerTransferLimited,
int powerBrickConnectionStatus) {
+ this(portId, supportedModes, supportedContaminantProtectionModes,
+ currentMode, canChangeMode,
+ currentPowerRole, canChangePowerRole,
+ currentDataRole, canChangeDataRole,
+ supportsEnableContaminantPresenceProtection, contaminantProtectionStatus,
+ supportsEnableContaminantPresenceDetection, contaminantDetectionStatus,
+ usbDataStatus, powerTransferLimited, powerBrickConnectionStatus,
+ false, new int[] {});
+ }
+
+ public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
+ int currentMode, boolean canChangeMode,
+ int currentPowerRole, boolean canChangePowerRole,
+ int currentDataRole, boolean canChangeDataRole,
+ boolean supportsEnableContaminantPresenceProtection,
+ int contaminantProtectionStatus,
+ boolean supportsEnableContaminantPresenceDetection,
+ int contaminantDetectionStatus,
+ int usbDataStatus,
+ boolean powerTransferLimited,
+ int powerBrickConnectionStatus,
+ boolean supportsComplianceWarnings,
+ int[] complianceWarnings) {
this.portId = portId;
this.supportedModes = supportedModes;
this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -84,6 +110,8 @@
this.usbDataStatus = usbDataStatus;
this.powerTransferLimited = powerTransferLimited;
this.powerBrickConnectionStatus = powerBrickConnectionStatus;
+ this.supportsComplianceWarnings = supportsComplianceWarnings;
+ this.complianceWarnings = complianceWarnings;
}
@Override
@@ -109,6 +137,8 @@
dest.writeInt(usbDataStatus);
dest.writeBoolean(powerTransferLimited);
dest.writeInt(powerBrickConnectionStatus);
+ dest.writeBoolean(supportsComplianceWarnings);
+ dest.writeIntArray(complianceWarnings);
}
public static final Parcelable.Creator<RawPortInfo> CREATOR =
@@ -131,6 +161,8 @@
int usbDataStatus = in.readInt();
boolean powerTransferLimited = in.readBoolean();
int powerBrickConnectionStatus = in.readInt();
+ boolean supportsComplianceWarnings = in.readBoolean();
+ int[] complianceWarnings = in.createIntArray();
return new RawPortInfo(id, supportedModes,
supportedContaminantProtectionModes, currentMode, canChangeMode,
currentPowerRole, canChangePowerRole,
@@ -139,7 +171,8 @@
contaminantProtectionStatus,
supportsEnableContaminantPresenceDetection,
contaminantDetectionStatus, usbDataStatus,
- powerTransferLimited, powerBrickConnectionStatus);
+ powerTransferLimited, powerBrickConnectionStatus,
+ supportsComplianceWarnings, complianceWarnings);
}
@Override
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 94273a3..ca11629 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -34,9 +34,12 @@
import android.hardware.usb.IUsbCallback;
import android.hardware.usb.PortRole;
import android.hardware.usb.PortStatus;
+import android.hardware.usb.ComplianceWarning;
+import android.os.Build;
import android.os.ServiceManager;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -46,6 +49,7 @@
import com.android.server.usb.UsbPortManager;
import com.android.server.usb.hal.port.RawPortInfo;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.concurrent.ThreadLocalRandom;
import java.util.NoSuchElementException;
@@ -551,6 +555,24 @@
return usbDataStatus;
}
+ private int[] formatComplianceWarnings(int[] complianceWarnings) {
+ Objects.requireNonNull(complianceWarnings);
+ IntArray newComplianceWarnings = new IntArray();
+ Arrays.sort(complianceWarnings);
+ for (int warning : complianceWarnings) {
+ if (newComplianceWarnings.indexOf(warning) == -1
+ && warning >= UsbPortStatus.COMPLIANCE_WARNING_OTHER) {
+ // ComplianceWarnings range from [1, 4] in Android U
+ if (warning > UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP) {
+ newComplianceWarnings.add(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ } else {
+ newComplianceWarnings.add(warning);
+ }
+ }
+ }
+ return newComplianceWarnings.toArray();
+ }
+
@Override
public void notifyPortStatusChange(
android.hardware.usb.PortStatus[] currentPortStatus, int retval) {
@@ -584,7 +606,9 @@
current.contaminantDetectionStatus,
toUsbDataStatusInt(current.usbDataStatus),
current.powerTransferLimited,
- current.powerBrickStatus);
+ current.powerBrickStatus,
+ current.supportsComplianceWarnings,
+ formatComplianceWarnings(current.complianceWarnings));
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: "
+ current.portName);
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
index 23d913c..10403c1 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -421,7 +421,8 @@
current.currentDataRole, current.canChangeDataRole,
false, CONTAMINANT_PROTECTION_NONE,
false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
- false, POWER_BRICK_STATUS_UNKNOWN);
+ false, POWER_BRICK_STATUS_UNKNOWN,
+ false, new int[] {});
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: "
+ current.portName);
@@ -455,7 +456,8 @@
current.status.currentDataRole, current.status.canChangeDataRole,
false, CONTAMINANT_PROTECTION_NONE,
false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
- false, POWER_BRICK_STATUS_UNKNOWN);
+ false, POWER_BRICK_STATUS_UNKNOWN,
+ false, new int[] {});
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: "
+ current.status.portName);
@@ -493,7 +495,8 @@
current.supportsEnableContaminantPresenceDetection,
current.contaminantDetectionStatus,
sUsbDataStatus,
- false, POWER_BRICK_STATUS_UNKNOWN);
+ false, POWER_BRICK_STATUS_UNKNOWN,
+ false, new int[] {});
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: "
+ current.status_1_1.status.portName);
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 2435243..86b98f1 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -5,7 +5,6 @@
import android.net.NetworkCapabilities;
import android.telecom.Connection;
import android.telephony.data.ApnSetting;
-import android.telephony.ims.ImsCallProfile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -495,7 +494,7 @@
PreciseCallState.PRECISE_CALL_STATE_HOLDING,
PreciseCallState.PRECISE_CALL_STATE_DIALING,
PreciseCallState.PRECISE_CALL_STATE_ALERTING,
- PreciseCallState.PRECISE_CALL_STATE_INCOMING,
+ PreciseCallState. PRECISE_CALL_STATE_INCOMING,
PreciseCallState.PRECISE_CALL_STATE_WAITING,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING})
@@ -728,36 +727,6 @@
})
public @interface ValidationStatus {}
- /**
- * IMS call Service types
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "SERVICE_TYPE_" }, value = {
- ImsCallProfile.SERVICE_TYPE_NONE,
- ImsCallProfile.SERVICE_TYPE_NORMAL,
- ImsCallProfile.SERVICE_TYPE_EMERGENCY,
- })
- public @interface ImsCallServiceType {}
-
- /**
- * IMS call types
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "CALL_TYPE_" }, value = {
- ImsCallProfile.CALL_TYPE_NONE,
- ImsCallProfile.CALL_TYPE_VOICE_N_VIDEO,
- ImsCallProfile.CALL_TYPE_VOICE,
- ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE,
- ImsCallProfile.CALL_TYPE_VT,
- ImsCallProfile.CALL_TYPE_VT_TX,
- ImsCallProfile.CALL_TYPE_VT_RX,
- ImsCallProfile.CALL_TYPE_VT_NODIR,
- ImsCallProfile.CALL_TYPE_VS,
- ImsCallProfile.CALL_TYPE_VS_TX,
- ImsCallProfile.CALL_TYPE_VS_RX,
- })
- public @interface ImsCallType {}
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 1dc64a9..b7bef39 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -29,10 +29,8 @@
* Contains information about a call's attributes as passed up from the HAL. If there are multiple
* ongoing calls, the CallAttributes will pertain to the call in the foreground.
* @hide
- * @deprecated use {@link CallState} for call information for each call.
*/
@SystemApi
-@Deprecated
public final class CallAttributes implements Parcelable {
private PreciseCallState mPreciseCallState;
@NetworkType
diff --git a/telephony/java/android/telephony/CallState.aidl b/telephony/java/android/telephony/CallState.aidl
deleted file mode 100644
index dd5af8e..0000000
--- a/telephony/java/android/telephony/CallState.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-parcelable CallState;
-
diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java
deleted file mode 100644
index 0a267cf..0000000
--- a/telephony/java/android/telephony/CallState.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.telephony.Annotation.ImsCallServiceType;
-import android.telephony.Annotation.ImsCallType;
-import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseCallStates;
-import android.telephony.ims.ImsCallProfile;
-import android.telephony.ims.ImsCallSession;
-
-import java.util.Objects;
-
-/**
- * Contains information about various states for a call.
- * @hide
- */
-@SystemApi
-public final class CallState implements Parcelable {
-
- /**
- * Call classifications are just used for backward compatibility of deprecated API {@link
- * TelephonyCallback#CallAttributesListener#onCallAttributesChanged}, Since these will be
- * removed when the deprecated API is removed, they should not be opened.
- */
- /**
- * Call classification is not valid. It should not be opened.
- * @hide
- */
- public static final int CALL_CLASSIFICATION_UNKNOWN = -1;
-
- /**
- * Call classification indicating foreground call
- * @hide
- */
- public static final int CALL_CLASSIFICATION_RINGING = 0;
-
- /**
- * Call classification indicating background call
- * @hide
- */
- public static final int CALL_CLASSIFICATION_FOREGROUND = 1;
-
- /**
- * Call classification indicating ringing call
- * @hide
- */
- public static final int CALL_CLASSIFICATION_BACKGROUND = 2;
-
- /**
- * Call classification Max value.
- * @hide
- */
- public static final int CALL_CLASSIFICATION_MAX = CALL_CLASSIFICATION_BACKGROUND + 1;
-
- @PreciseCallStates
- private final int mPreciseCallState;
-
- @NetworkType
- private final int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints
- private final CallQuality mCallQuality;
-
- private final int mCallClassification;
- /**
- * IMS call session ID. {@link ImsCallSession#getCallId()}
- */
- @Nullable
- private String mImsCallId;
-
- /**
- * IMS call service type of this call
- */
- @ImsCallServiceType
- private int mImsCallServiceType;
-
- /**
- * IMS call type of this call.
- */
- @ImsCallType
- private int mImsCallType;
-
- /**
- * Constructor of CallAttributes
- *
- * @param callState call state defined in {@link PreciseCallState}
- * @param networkType network type for this call attributes
- * @param callQuality call quality for this call attributes, only CallState in
- * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call
- * quality.
- * @param callClassification call classification
- * @param imsCallId IMS call session ID for this call attributes
- * @param imsCallServiceType IMS call service type for this call attributes
- * @param imsCallType IMS call type for this call attributes
- */
- private CallState(@PreciseCallStates int callState, @NetworkType int networkType,
- @NonNull CallQuality callQuality, int callClassification, @Nullable String imsCallId,
- @ImsCallServiceType int imsCallServiceType, @ImsCallType int imsCallType) {
- this.mPreciseCallState = callState;
- this.mNetworkType = networkType;
- this.mCallQuality = callQuality;
- this.mCallClassification = callClassification;
- this.mImsCallId = imsCallId;
- this.mImsCallServiceType = imsCallServiceType;
- this.mImsCallType = imsCallType;
- }
-
- @NonNull
- @Override
- public String toString() {
- return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
- + " mCallQuality=" + mCallQuality + " mCallClassification" + mCallClassification
- + " mImsCallId=" + mImsCallId + " mImsCallServiceType=" + mImsCallServiceType
- + " mImsCallType=" + mImsCallType;
- }
-
- private CallState(Parcel in) {
- this.mPreciseCallState = in.readInt();
- this.mNetworkType = in.readInt();
- this.mCallQuality = in.readParcelable(
- CallQuality.class.getClassLoader(), CallQuality.class);
- this.mCallClassification = in.readInt();
- this.mImsCallId = in.readString();
- this.mImsCallServiceType = in.readInt();
- this.mImsCallType = in.readInt();
- }
-
- // getters
- /**
- * Returns the precise call state of the call.
- */
- @PreciseCallStates
- public int getCallState() {
- return mPreciseCallState;
- }
-
- /**
- * Returns the {@link TelephonyManager#NetworkType} of the call.
- *
- * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
- * @see TelephonyManager#NETWORK_TYPE_GPRS
- * @see TelephonyManager#NETWORK_TYPE_EDGE
- * @see TelephonyManager#NETWORK_TYPE_UMTS
- * @see TelephonyManager#NETWORK_TYPE_CDMA
- * @see TelephonyManager#NETWORK_TYPE_EVDO_0
- * @see TelephonyManager#NETWORK_TYPE_EVDO_A
- * @see TelephonyManager#NETWORK_TYPE_1xRTT
- * @see TelephonyManager#NETWORK_TYPE_HSDPA
- * @see TelephonyManager#NETWORK_TYPE_HSUPA
- * @see TelephonyManager#NETWORK_TYPE_HSPA
- * @see TelephonyManager#NETWORK_TYPE_IDEN
- * @see TelephonyManager#NETWORK_TYPE_EVDO_B
- * @see TelephonyManager#NETWORK_TYPE_LTE
- * @see TelephonyManager#NETWORK_TYPE_EHRPD
- * @see TelephonyManager#NETWORK_TYPE_HSPAP
- * @see TelephonyManager#NETWORK_TYPE_GSM
- * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA
- * @see TelephonyManager#NETWORK_TYPE_IWLAN
- * @see TelephonyManager#NETWORK_TYPE_LTE_CA
- * @see TelephonyManager#NETWORK_TYPE_NR
- */
- @NetworkType
- public int getNetworkType() {
- return mNetworkType;
- }
-
- /**
- * Returns the {#link CallQuality} of the call.
- * @return call quality for this call attributes, only CallState in {@link PreciseCallState#
- * PRECISE_CALL_STATE_ACTIVE} will have valid call quality. It will be null for the
- * call which is not in {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}.
- */
- @Nullable
- public CallQuality getCallQuality() {
- return mCallQuality;
- }
-
- /**
- * Returns the call classification.
- * @hide
- */
- public int getCallClassification() {
- return mCallClassification;
- }
-
- /**
- * Returns the IMS call session ID.
- */
- @Nullable
- public String getImsCallSessionId() {
- return mImsCallId;
- }
-
- /**
- * Returns the IMS call service type.
- */
- @ImsCallServiceType
- public int getImsCallServiceType() {
- return mImsCallServiceType;
- }
-
- /**
- * Returns the IMS call type.
- */
- @ImsCallType
- public int getImsCallType() {
- return mImsCallType;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality, mCallClassification,
- mImsCallId, mImsCallServiceType, mImsCallType);
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (o == null || !(o instanceof CallState) || hashCode() != o.hashCode()) {
- return false;
- }
-
- if (this == o) {
- return true;
- }
-
- CallState s = (CallState) o;
-
- return (Objects.equals(mPreciseCallState, s.mPreciseCallState)
- && mPreciseCallState == s.mPreciseCallState
- && mNetworkType == s.mNetworkType
- && Objects.equals(mCallQuality, s.mCallQuality)
- && mCallClassification == s.mCallClassification
- && Objects.equals(mImsCallId, s.mImsCallId)
- && mImsCallType == s.mImsCallType
- && mImsCallServiceType == s.mImsCallServiceType);
- }
-
- /**
- * {@link Parcelable#describeContents}
- */
- public int describeContents() {
- return 0;
- }
-
- /**
- * {@link Parcelable#writeToParcel}
- */
- public void writeToParcel(@Nullable Parcel dest, int flags) {
- dest.writeInt(mPreciseCallState);
- dest.writeInt(mNetworkType);
- dest.writeParcelable(mCallQuality, flags);
- dest.writeInt(mCallClassification);
- dest.writeString(mImsCallId);
- dest.writeInt(mImsCallServiceType);
- dest.writeInt(mImsCallType);
- }
-
- public static final @NonNull Creator<CallState> CREATOR = new Creator() {
- public CallState createFromParcel(Parcel in) {
- return new CallState(in);
- }
-
- public CallState[] newArray(int size) {
- return new CallState[size];
- }
- };
-
- /**
- * Builder of {@link CallState}
- *
- * <p>The example below shows how you might create a new {@code CallState}:
- *
- * <pre><code>
- *
- * CallState = new CallState.Builder()
- * .setCallState(3)
- * .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE})
- * .setCallQuality({@link CallQuality})
- * .setImsCallSessionId({@link String})
- * .setImsCallServiceType({@link ImsCallProfile#SERVICE_TYPE_NORMAL})
- * .setImsCallType({@link ImsCallProfile#CALL_TYPE_VOICE})
- * .build();
- * </code></pre>
- */
- public static final class Builder {
- private @PreciseCallStates int mPreciseCallState;
- private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- private CallQuality mCallQuality = null;
- private int mCallClassification = CALL_CLASSIFICATION_UNKNOWN;
- private String mImsCallId;
- private @ImsCallServiceType int mImsCallServiceType = ImsCallProfile.SERVICE_TYPE_NONE;
- private @ImsCallType int mImsCallType = ImsCallProfile.CALL_TYPE_NONE;
-
-
- /**
- * Default constructor for the Builder.
- */
- public Builder(@PreciseCallStates int preciseCallState) {
- mPreciseCallState = preciseCallState;
- }
-
- /**
- * Set network type of this call.
- *
- * @param networkType the transport type.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setNetworkType(@NetworkType int networkType) {
- this.mNetworkType = networkType;
- return this;
- }
-
- /**
- * Set the call quality {@link CallQuality} of this call.
- *
- * @param callQuality call quality of active call.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setCallQuality(@Nullable CallQuality callQuality) {
- this.mCallQuality = callQuality;
- return this;
- }
-
- /**
- * Set call classification for this call.
- *
- * @param classification call classification type defined in this class.
- * @return The same instance of the builder.
- * @hide
- */
- @NonNull
- public CallState.Builder setCallClassification(int classification) {
- this.mCallClassification = classification;
- return this;
- }
-
- /**
- * Set IMS call session ID of this call.
- *
- * @param imsCallId IMS call session ID.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setImsCallSessionId(@Nullable String imsCallId) {
- this.mImsCallId = imsCallId;
- return this;
- }
-
- /**
- * Set IMS call service type of this call.
- *
- * @param serviceType IMS call service type defined in {@link ImsCallProfile}.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setImsCallServiceType(@ImsCallServiceType int serviceType) {
- this.mImsCallServiceType = serviceType;
- return this;
- }
-
- /**
- * Set IMS call type of this call.
- *
- * @param callType IMS call type defined in {@link ImsCallProfile}.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setImsCallType(@ImsCallType int callType) {
- this.mImsCallType = callType;
- return this;
- }
-
- /**
- * Build the {@link CallState}
- *
- * @return the {@link CallState} object
- */
- @NonNull
- public CallState build() {
- return new CallState(
- mPreciseCallState,
- mNetworkType,
- mCallQuality,
- mCallClassification,
- mImsCallId,
- mImsCallServiceType,
- mImsCallType);
- }
- }
-}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 1ea7fdc..e6d7df3 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -78,9 +78,8 @@
public static final int SERVICE_TYPE_EMERGENCY = 2;
/**
- * Call type none
+ * Call types
*/
- public static final int CALL_TYPE_NONE = 0;
/**
* IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
*/
diff --git a/tools/fonts/font-scaling-array-generator.js b/tools/fonts/font-scaling-array-generator.js
new file mode 100644
index 0000000..9754697
--- /dev/null
+++ b/tools/fonts/font-scaling-array-generator.js
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+/**
+ Generates arrays for non-linear font scaling, to be pasted into
+ frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java
+
+ To use:
+ `node font-scaling-array-generator.js`
+ or just open a browser, open DevTools, and paste into the Console.
+*/
+
+/**
+ * Modify this to match your packages/apps/Settings/res/arrays.xml#entryvalues_font_size
+ * array so that all possible scales are generated.
+ */
+const scales = [1.15, 1.30, 1.5, 1.8, 2];
+
+const commonSpSizes = [8, 10, 12, 14, 18, 20, 24, 30, 100];
+
+/**
+ * Enum for GENERATION_STYLE which determines how to generate the arrays.
+ */
+const GenerationStyle = {
+ /**
+ * Interpolates between hand-tweaked curves. This is the best option and
+ * shouldn't require any additional tweaking.
+ */
+ CUSTOM_TWEAKED: 'CUSTOM_TWEAKED',
+
+ /**
+ * Uses a curve equation that is mostly correct, but will need manual tweaking
+ * at some scales.
+ */
+ CURVE: 'CURVE',
+
+ /**
+ * Uses straight linear multiplication. Good starting point for manual
+ * tweaking.
+ */
+ LINEAR: 'LINEAR'
+}
+
+/**
+ * Determines how arrays are generated. Must be one of the GenerationStyle
+ * values.
+ */
+const GENERATION_STYLE = GenerationStyle.CUSTOM_TWEAKED;
+
+// These are hand-tweaked curves from which we will derive the other
+// interstitial curves using linear interpolation, in the case of using
+// GenerationStyle.CUSTOM_TWEAKED.
+const interpolationTargets = {
+ 1.0: commonSpSizes,
+ 1.5: [12, 15, 18, 22, 24, 26, 28, 30, 100],
+ 2.0: [16, 20, 24, 26, 30, 34, 36, 38, 100]
+};
+
+/**
+ * Interpolate a value with specified extrema, to a new value between new
+ * extrema.
+ *
+ * @param value the current value
+ * @param inputMin minimum the input value can reach
+ * @param inputMax maximum the input value can reach
+ * @param outputMin minimum the output value can reach
+ * @param outputMax maximum the output value can reach
+ */
+function map(value, inputMin, inputMax, outputMin, outputMax) {
+ return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin));
+}
+
+/***
+ * Interpolate between values a and b.
+ */
+function lerp(a, b, fraction) {
+ return (a * (1.0 - fraction)) + (b * fraction);
+}
+
+function generateRatios(scale) {
+ // Find the best two arrays to interpolate between.
+ let startTarget, endTarget;
+ let startTargetScale, endTargetScale;
+ const targetScales = Object.keys(interpolationTargets).sort();
+ for (let i = 0; i < targetScales.length - 1; i++) {
+ const targetScaleKey = targetScales[i];
+ const targetScale = parseFloat(targetScaleKey, 10);
+ const startTargetScaleKey = targetScaleKey;
+ const endTargetScaleKey = targetScales[i + 1];
+
+ if (scale < parseFloat(startTargetScaleKey, 10)) {
+ break;
+ }
+
+ startTargetScale = parseFloat(startTargetScaleKey, 10);
+ endTargetScale = parseFloat(endTargetScaleKey, 10);
+ startTarget = interpolationTargets[startTargetScaleKey];
+ endTarget = interpolationTargets[endTargetScaleKey];
+ }
+ const interpolationProgress = map(scale, startTargetScale, endTargetScale, 0, 1);
+
+ return commonSpSizes.map((sp, i) => {
+ const originalSizeDp = sp;
+ let newSizeDp;
+ switch (GENERATION_STYLE) {
+ case GenerationStyle.CUSTOM_TWEAKED:
+ newSizeDp = lerp(startTarget[i], endTarget[i], interpolationProgress);
+ break;
+ case GenerationStyle.CURVE: {
+ let coeff1;
+ let coeff2;
+ if (scale < 1) {
+ // \left(1.22^{-\left(x+5\right)}+0.5\right)\cdot x
+ coeff1 = -5;
+ coeff2 = scale;
+ } else {
+ // (1.22^{-\left(x-10\right)}+1\right)\cdot x
+ coeff1 = map(scale, 1, 2, 2, 8);
+ coeff2 = 1;
+ }
+ newSizeDp = ((Math.pow(1.22, (-(originalSizeDp - coeff1))) + coeff2) * originalSizeDp);
+ break;
+ }
+ case GenerationStyle.LINEAR:
+ newSizeDp = originalSizeDp * scale;
+ break;
+ default:
+ throw new Error('Invalid GENERATION_STYLE');
+ }
+ return {
+ fromSp: sp,
+ toDp: newSizeDp
+ }
+ });
+}
+
+const scaleArrays =
+ scales
+ .map(scale => {
+ const scaleString = (scale * 100).toFixed(0);
+ return {
+ scale,
+ name: `font_size_original_sp_to_scaled_dp_${scaleString}_percent`
+ }
+ })
+ .map(scaleArray => {
+ const items = generateRatios(scaleArray.scale);
+
+ return {
+ ...scaleArray,
+ items
+ }
+ });
+
+function formatDigit(d) {
+ const twoSignificantDigits = Math.round(d * 100) / 100;
+ return String(twoSignificantDigits).padStart(4, ' ');
+}
+
+console.log(
+ '' +
+ scaleArrays.reduce(
+ (previousScaleArray, currentScaleArray) => {
+ const itemsFromSp = currentScaleArray.items.map(d => d.fromSp)
+ .map(formatDigit)
+ .join('f, ');
+ const itemsToDp = currentScaleArray.items.map(d => d.toDp)
+ .map(formatDigit)
+ .join('f, ');
+
+ return previousScaleArray + `
+ put(
+ /* scaleKey= */ ${currentScaleArray.scale}f,
+ new FontScaleConverter(
+ /* fromSp= */
+ new float[] {${itemsFromSp}},
+ /* toDp= */
+ new float[] {${itemsToDp}})
+ );
+ `;
+ },
+ ''));