Merge "TARE Factors: Keys and Default Values"
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 74ed334..829ad27 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -26,6 +26,8 @@
import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
import static com.android.server.tare.EconomicPolicy.eventToString;
import static com.android.server.tare.EconomicPolicy.getEventType;
+import static com.android.server.tare.TareUtils.appToString;
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import static com.android.server.tare.TareUtils.narcToString;
import android.annotation.NonNull;
@@ -183,7 +185,7 @@
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
final long nowElapsed = SystemClock.elapsedRealtime();
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
mTotalDeltaCalculator.reset(ledger, nowElapsed, now);
ongoingEvents.forEach(mTotalDeltaCalculator);
balance += mTotalDeltaCalculator.mTotal;
@@ -194,7 +196,12 @@
@GuardedBy("mLock")
void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName,
final int eventId, @Nullable String tag) {
- final long now = System.currentTimeMillis();
+ if (mIrs.isSystem(userId, pkgName)) {
+ // Events are free for the system. Don't bother recording them.
+ return;
+ }
+
+ final long now = getCurrentTimeMillis();
final Ledger ledger = getLedgerLocked(userId, pkgName);
final int eventType = getEventType(eventId);
@@ -278,7 +285,7 @@
@GuardedBy("mLock")
void onDeviceStateChangedLocked() {
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
mCurrentOngoingEvents.forEach((userId, pkgName, ongoingEvents) -> {
@@ -326,7 +333,7 @@
@GuardedBy("mLock")
void onAppStatesChangedLocked(final int userId, @NonNull ArraySet<String> pkgNames) {
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
for (int i = 0; i < pkgNames.size(); ++i) {
@@ -415,12 +422,15 @@
}
ongoingEvent.refCount--;
if (ongoingEvent.refCount <= 0) {
- final long startElapsed = ongoingEvent.startTimeElapsed;
- final long startTime = now - (nowElapsed - startElapsed);
- final long actualDelta = getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
- recordTransactionLocked(userId, pkgName, ledger,
- new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
- notifyOnAffordabilityChange);
+ if (!mIrs.isSystem(userId, pkgName)) {
+ final long startElapsed = ongoingEvent.startTimeElapsed;
+ final long startTime = now - (nowElapsed - startElapsed);
+ final long actualDelta =
+ getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
+ recordTransactionLocked(userId, pkgName, ledger,
+ new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
+ notifyOnAffordabilityChange);
+ }
ongoingEvents.delete(eventId, tag);
}
if (updateBalanceCheck) {
@@ -446,6 +456,15 @@
private void recordTransactionLocked(final int userId, @NonNull final String pkgName,
@NonNull Ledger ledger, @NonNull Ledger.Transaction transaction,
final boolean notifyOnAffordabilityChange) {
+ if (transaction.delta == 0) {
+ // Skip recording transactions with a delta of 0 to save on space.
+ return;
+ }
+ if (mIrs.isSystem(userId, pkgName)) {
+ Slog.wtfStack(TAG,
+ "Tried to adjust system balance for " + appToString(userId, pkgName));
+ return;
+ }
final long maxCirculationAllowed = mIrs.getMaxCirculationLocked();
final long newArcsInCirculation = mCurrentNarcsInCirculation + transaction.delta;
if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) {
@@ -477,7 +496,7 @@
// The earliest transaction won't change until we clean up the ledger, so no point
// continuing to reschedule an existing cleanup.
final long cleanupAlarmElapsed = SystemClock.elapsedRealtime() + MAX_TRANSACTION_AGE_MS
- - (System.currentTimeMillis() - ledger.getEarliestTransaction().endTimeMs);
+ - (getCurrentTimeMillis() - ledger.getEarliestTransaction().endTimeMs);
mLedgerCleanupAlarmListener.addAlarmLocked(userId, pkgName, cleanupAlarmElapsed);
}
// TODO: save changes to disk in a background thread
@@ -509,7 +528,7 @@
@GuardedBy("mLock")
void reclaimUnusedAssetsLocked(double percentage) {
final List<PackageInfo> pkgs = mIrs.getInstalledPackages();
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
final int userId = UserHandle.getUserId(pkgs.get(i).applicationInfo.uid);
final String pkgName = pkgs.get(i).packageName;
@@ -543,11 +562,15 @@
@GuardedBy("mLock")
void distributeBasicIncomeLocked(int batteryLevel) {
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
final PackageInfo pkgInfo = pkgs.get(i);
final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
final String pkgName = pkgInfo.packageName;
+ if (mIrs.isSystem(userId, pkgName)) {
+ // No point allocating ARCs to the system. It can do whatever it wants.
+ continue;
+ }
Ledger ledger = getLedgerLocked(userId, pkgName);
final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
final double perc = batteryLevel / 100d;
@@ -578,12 +601,16 @@
List<PackageInfo> pkgs = packageManager.getInstalledPackagesAsUser(0, userId);
final long maxBirthright =
mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
final PackageInfo packageInfo = pkgs.get(i);
final String pkgName = packageInfo.packageName;
final Ledger ledger = getLedgerLocked(userId, pkgName);
+ if (mIrs.isSystem(userId, pkgName)) {
+ // No point allocating ARCs to the system. It can do whatever it wants.
+ continue;
+ }
if (ledger.getCurrentBalance() > 0) {
// App already got credits somehow. Move along.
Slog.wtf(TAG, "App " + pkgName + " had credits before economy was set up");
@@ -609,7 +636,7 @@
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
final int numPackages = pkgs.size();
final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
index 831c05f..29aa946 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
@@ -124,6 +124,11 @@
*/
boolean canPayFor(int userId, @NonNull String pkgName, @NonNull ActionBill bill);
+ /**
+ * Returns the maximum duration (in milliseconds) that the specified app can afford the bill,
+ * based on current prices.
+ */
+ long getMaxDurationMs(int userId, @NonNull String pkgName, @NonNull ActionBill bill);
/**
* Register an {@link AffordabilityChangeListener} to track when an app's ability to afford the
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 67d7a95..a2ba75c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -19,6 +19,8 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
@@ -30,6 +32,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManagerInternal;
@@ -45,6 +48,7 @@
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArrayMap;
import android.util.SparseSetArray;
import com.android.internal.annotations.GuardedBy;
@@ -81,6 +85,7 @@
private final Handler mHandler;
private final BatteryManagerInternal mBatteryManagerInternal;
private final PackageManager mPackageManager;
+ private final PackageManagerInternal mPackageManagerInternal;
private final Agent mAgent;
private final CompleteEconomicPolicy mCompleteEconomicPolicy;
@@ -95,6 +100,10 @@
@GuardedBy("mLock")
private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>();
+ /** Cached mapping of userId+package to their UIDs (for all users) */
+ @GuardedBy("mPackageToUidCache")
+ private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>();
+
private volatile boolean mIsEnabled;
private volatile int mBootPhase;
// In the range [0,100] to represent 0% to 100% battery.
@@ -104,7 +113,6 @@
@GuardedBy("mLock")
private long mLastUnusedReclamationTime;
- @SuppressWarnings("FieldCanBeLocal")
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Nullable
private String getPackageName(Intent intent) {
@@ -159,7 +167,7 @@
public void onAlarm() {
synchronized (mLock) {
mAgent.reclaimUnusedAssetsLocked(DEFAULT_UNUSED_RECLAMATION_PERCENTAGE);
- mLastUnusedReclamationTime = System.currentTimeMillis();
+ mLastUnusedReclamationTime = getCurrentTimeMillis();
scheduleUnusedWealthReclamationLocked();
}
}
@@ -185,6 +193,7 @@
mHandler = new IrsHandler(TareHandlerThread.get().getLooper());
mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
mPackageManager = context.getPackageManager();
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mEconomyManagerStub = new EconomyManagerStub();
mCompleteEconomicPolicy = new CompleteEconomicPolicy(this);
mAgent = new Agent(this, mCompleteEconomicPolicy);
@@ -232,10 +241,28 @@
/ 100;
}
+ int getUid(final int userId, @NonNull final String pkgName) {
+ synchronized (mPackageToUidCache) {
+ Integer uid = mPackageToUidCache.get(userId, pkgName);
+ if (uid == null) {
+ uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
+ mPackageToUidCache.add(userId, pkgName, uid);
+ }
+ return uid;
+ }
+ }
+
boolean isEnabled() {
return mIsEnabled;
}
+ boolean isSystem(final int userId, @NonNull String pkgName) {
+ if ("android".equals(pkgName)) {
+ return true;
+ }
+ return UserHandle.isCore(getUid(userId, pkgName));
+ }
+
void onBatteryLevelChanged() {
synchronized (mLock) {
final int newBatteryLevel = getCurrentBatteryLevel();
@@ -261,6 +288,9 @@
Slog.wtf(TAG, "PM couldn't find newly added package: " + pkgName);
return;
}
+ synchronized (mPackageToUidCache) {
+ mPackageToUidCache.add(userId, pkgName, uid);
+ }
synchronized (mLock) {
mPkgCache.add(packageInfo);
mUidToPackageCache.add(uid, pkgName);
@@ -277,6 +307,9 @@
void onPackageRemoved(final int uid, @NonNull final String pkgName) {
final int userId = UserHandle.getUserId(uid);
+ synchronized (mPackageToUidCache) {
+ mPackageToUidCache.delete(userId, pkgName);
+ }
synchronized (mLock) {
mUidToPackageCache.remove(uid, pkgName);
for (int i = 0; i < mPkgCache.size(); ++i) {
@@ -342,7 +375,7 @@
@GuardedBy("mLock")
private void scheduleUnusedWealthReclamationLocked() {
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
final long nextReclamationTime =
Math.max(mLastUnusedReclamationTime + UNUSED_RECLAMATION_PERIOD_MS, now + 30_000);
mHandler.post(() -> {
@@ -447,6 +480,9 @@
mUidToPackageCache.clear();
getContext().unregisterReceiver(mBroadcastReceiver);
}
+ synchronized (mPackageToUidCache) {
+ mPackageToUidCache.clear();
+ }
}
private final class IrsHandler extends Handler {
@@ -514,9 +550,22 @@
}
private final class LocalService implements EconomyManagerInternal {
+ /**
+ * Use an extremely large value to indicate that an app can pay for a bill indefinitely.
+ * The value set here should be large/long enough that there's no reasonable expectation
+ * of a device operating uninterrupted (or in the exact same state) for that period of time.
+ * We intentionally don't use Long.MAX_VALUE to avoid potential overflow if a client
+ * doesn't check the value and just immediately adds it to the current time.
+ */
+ private static final long FOREVER_MS = 27 * 365 * 24 * HOUR_IN_MILLIS;
+
@Override
public void registerAffordabilityChangeListener(int userId, @NonNull String pkgName,
@NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) {
+ if (isSystem(userId, pkgName)) {
+ // The system's affordability never changes.
+ return;
+ }
synchronized (mLock) {
mAgent.registerAffordabilityChangeListenerLocked(userId, pkgName, listener, bill);
}
@@ -525,6 +574,10 @@
@Override
public void unregisterAffordabilityChangeListener(int userId, @NonNull String pkgName,
@NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) {
+ if (isSystem(userId, pkgName)) {
+ // The system's affordability never changes.
+ return;
+ }
synchronized (mLock) {
mAgent.unregisterAffordabilityChangeListenerLocked(userId, pkgName, listener, bill);
}
@@ -535,6 +588,11 @@
if (!mIsEnabled) {
return true;
}
+ if (isSystem(userId, pkgName)) {
+ // The government, I mean the system, can create ARCs as it needs to in order to
+ // operate.
+ return true;
+ }
// TODO: take temp-allowlist into consideration
long requiredBalance = 0;
final List<EconomyManagerInternal.AnticipatedAction> projectedActions =
@@ -552,6 +610,32 @@
}
@Override
+ public long getMaxDurationMs(int userId, @NonNull String pkgName,
+ @NonNull ActionBill bill) {
+ if (!mIsEnabled) {
+ return FOREVER_MS;
+ }
+ if (isSystem(userId, pkgName)) {
+ return FOREVER_MS;
+ }
+ long totalCostPerSecond = 0;
+ final List<EconomyManagerInternal.AnticipatedAction> projectedActions =
+ bill.getAnticipatedActions();
+ for (int i = 0; i < projectedActions.size(); ++i) {
+ AnticipatedAction action = projectedActions.get(i);
+ final long cost =
+ mCompleteEconomicPolicy.getCostOfAction(action.actionId, userId, pkgName);
+ totalCostPerSecond += cost;
+ }
+ if (totalCostPerSecond == 0) {
+ return FOREVER_MS;
+ }
+ synchronized (mLock) {
+ return mAgent.getBalanceLocked(userId, pkgName) * 1000 / totalCostPerSecond;
+ }
+ }
+
+ @Override
public void noteInstantaneousEvent(int userId, @NonNull String pkgName, int eventId,
@Nullable String tag) {
if (!mIsEnabled) {
@@ -581,7 +665,7 @@
return;
}
final long nowElapsed = SystemClock.elapsedRealtime();
- final long now = System.currentTimeMillis();
+ final long now = getCurrentTimeMillis();
synchronized (mLock) {
mAgent.stopOngoingActionLocked(userId, pkgName, eventId, tag, nowElapsed, now);
}
@@ -630,6 +714,9 @@
private void dumpInternal(final IndentingPrintWriter pw) {
synchronized (mLock) {
+ pw.print("Is enabled: ");
+ pw.println(mIsEnabled);
+
pw.print("Current battery level: ");
pw.println(mCurrentBatteryLevel);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index ae1bf26..76543d7 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -19,6 +19,7 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.server.tare.TareUtils.dumpTime;
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import static com.android.server.tare.TareUtils.narcToString;
import android.annotation.NonNull;
@@ -109,8 +110,8 @@
/** Deletes transactions that are older than {@code minAgeMs}. */
void removeOldTransactions(long minAgeMs) {
- final long cutoff = System.currentTimeMillis() - minAgeMs;
- while (mTransactions.get(0).endTimeMs <= cutoff) {
+ final long cutoff = getCurrentTimeMillis() - minAgeMs;
+ while (mTransactions.size() > 0 && mTransactions.get(0).endTimeMs <= cutoff) {
mTransactions.remove(0);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
index 92a2014a..67a3dc6 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.IUidObserver;
-import android.content.pm.PackageManagerInternal;
import android.os.RemoteException;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -28,7 +27,6 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -56,7 +54,6 @@
private final Object mLock = new Object();
private final InternalResourceService mIrs;
- private final PackageManagerInternal mPackageManagerInternal;
/** Cached mapping of userId+package to their UIDs (for all users) */
private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>();
@@ -105,7 +102,6 @@
ProcessStateModifier(@NonNull InternalResourceService irs) {
super();
mIrs = irs;
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
@Override
@@ -143,7 +139,7 @@
final int procState;
synchronized (mLock) {
procState = mUidProcStateBucketCache.get(
- getUidLocked(userId, pkgName), PROC_STATE_BUCKET_NONE);
+ mIrs.getUid(userId, pkgName), PROC_STATE_BUCKET_NONE);
}
switch (procState) {
case PROC_STATE_BUCKET_TOP:
@@ -166,15 +162,6 @@
pw.println(mUidProcStateBucketCache);
}
- @GuardedBy("mLock")
- private int getUidLocked(final int userId, @NonNull final String pkgName) {
- if (!mPackageToUidCache.contains(userId, pkgName)) {
- mPackageToUidCache.add(userId, pkgName,
- mPackageManagerInternal.getPackageUid(pkgName, 0, userId));
- }
- return mPackageToUidCache.get(userId, pkgName);
- }
-
@ProcStateBucket
private int getProcStateBucket(int procState) {
if (procState <= ActivityManager.PROCESS_STATE_TOP) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
index 2d72f56..1e047aa 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
@@ -20,7 +20,10 @@
import android.annotation.SuppressLint;
import android.util.IndentingPrintWriter;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.text.SimpleDateFormat;
+import java.time.Clock;
class TareUtils {
private static final long NARC_IN_ARC = 1_000_000_000L;
@@ -29,6 +32,9 @@
private static final SimpleDateFormat sDumpDateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ @VisibleForTesting
+ static Clock sSystemClock = Clock.systemUTC();
+
static long arcToNarc(int arcs) {
return arcs * NARC_IN_ARC;
}
@@ -37,6 +43,10 @@
pw.print(sDumpDateFormat.format(time));
}
+ static long getCurrentTimeMillis() {
+ return sSystemClock.millis();
+ }
+
static int narcToArc(long narcs) {
return (int) (narcs / NARC_IN_ARC);
}
@@ -59,4 +69,10 @@
sb.append(" ARCs");
return sb.toString();
}
+
+ /** Returns a standardized format for printing userId+pkgName combinations. */
+ @NonNull
+ static String appToString(int userId, String pkgName) {
+ return "<" + userId + ">" + pkgName;
+ }
}
diff --git a/core/api/current.txt b/core/api/current.txt
index c7791d9..396c080 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31684,6 +31684,7 @@
method public boolean isDeviceIdleMode();
method public boolean isIgnoringBatteryOptimizations(String);
method public boolean isInteractive();
+ method public boolean isLightDeviceIdleMode();
method public boolean isPowerSaveMode();
method public boolean isRebootingUserspaceSupported();
method @Deprecated public boolean isScreenOn();
@@ -31694,6 +31695,7 @@
method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
+ field public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -34832,6 +34834,7 @@
}
public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
+ field public static final String ACTION_SET_DEFAULT_ACCOUNT = "android.provider.action.SET_DEFAULT_ACCOUNT";
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
field public static final android.net.Uri CONTENT_URI;
@@ -51941,7 +51944,7 @@
method public boolean finishComposingText();
method public int getCursorCapsMode(int);
method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
- method public android.os.Handler getHandler();
+ method @Nullable public android.os.Handler getHandler();
method public CharSequence getSelectedText(int);
method @Nullable public default android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 45db0f6..fd6589c 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2112,7 +2112,8 @@
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
- + "; have you declared this activity in your AndroidManifest.xml?");
+ + "; have you declared this activity in your AndroidManifest.xml"
+ + ", or does your intent not match its declared <intent-filter>?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
case ActivityManager.START_PERMISSION_DENIED:
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 27db25d..9a37d04 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -13736,8 +13736,6 @@
*
* It is recommended that the Enterprise ID is at least 6 characters long, and no more than
* 64 characters.
- * This API is supposed to be called only after the boot phase is complete,
- * throws {@link IllegalStateException} if called before boot phase is complete.
*
* @param enterpriseId An identifier of the organization this work profile or device is
* enrolled into.
diff --git a/core/java/android/appwidget/AppWidgetManagerInternal.java b/core/java/android/appwidget/AppWidgetManagerInternal.java
index 266e33a..5694ca8 100644
--- a/core/java/android/appwidget/AppWidgetManagerInternal.java
+++ b/core/java/android/appwidget/AppWidgetManagerInternal.java
@@ -19,8 +19,6 @@
import android.annotation.Nullable;
import android.util.ArraySet;
-import java.util.Set;
-
/**
* App widget manager local system service interface.
*
@@ -44,16 +42,4 @@
* @param userId The user that is being unlocked.
*/
public abstract void unlockUser(int userId);
-
- /**
- * Updates all widgets, applying changes to Runtime Resource Overlay affecting the specified
- * target packages.
- *
- * @param packageNames The names of all target packages for which an overlay was modified
- * @param userId The user for which overlay modifications occurred.
- * @param updateFrameworkRes Whether or not an overlay affected the values of framework
- * resources.
- */
- public abstract void applyResourceOverlaysToWidgets(Set<String> packageNames, int userId,
- boolean updateFrameworkRes);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index bed0383..29ce397 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -55,6 +55,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.split.SplitAssetLoader;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.ApkAssets;
@@ -7425,7 +7427,7 @@
mCompileSdkVersionCodename = dest.readString();
mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
- mKeySetMapping = readKeySetMapping(dest);
+ mKeySetMapping = ParsingPackageUtils.readKeySetMapping(dest);
cpuAbiOverride = dest.readString();
use32bitAbi = (dest.readInt() == 1);
@@ -7551,73 +7553,13 @@
dest.writeInt(mCompileSdkVersion);
dest.writeString(mCompileSdkVersionCodename);
dest.writeArraySet(mUpgradeKeySets);
- writeKeySetMapping(dest, mKeySetMapping);
+ ParsingPackageUtils.writeKeySetMapping(dest, mKeySetMapping);
dest.writeString(cpuAbiOverride);
dest.writeInt(use32bitAbi ? 1 : 0);
dest.writeByteArray(restrictUpdateHash);
dest.writeInt(visibleToInstantApps ? 1 : 0);
}
- /**
- * Writes the keyset mapping to the provided package. {@code null} mappings are permitted.
- */
- private static void writeKeySetMapping(
- Parcel dest, ArrayMap<String, ArraySet<PublicKey>> keySetMapping) {
- if (keySetMapping == null) {
- dest.writeInt(-1);
- return;
- }
-
- final int N = keySetMapping.size();
- dest.writeInt(N);
-
- for (int i = 0; i < N; i++) {
- dest.writeString(keySetMapping.keyAt(i));
- ArraySet<PublicKey> keys = keySetMapping.valueAt(i);
- if (keys == null) {
- dest.writeInt(-1);
- continue;
- }
-
- final int M = keys.size();
- dest.writeInt(M);
- for (int j = 0; j < M; j++) {
- dest.writeSerializable(keys.valueAt(j));
- }
- }
- }
-
- /**
- * Reads a keyset mapping from the given parcel at the given data position. May return
- * {@code null} if the serialized mapping was {@code null}.
- */
- private static ArrayMap<String, ArraySet<PublicKey>> readKeySetMapping(Parcel in) {
- final int N = in.readInt();
- if (N == -1) {
- return null;
- }
-
- ArrayMap<String, ArraySet<PublicKey>> keySetMapping = new ArrayMap<>();
- for (int i = 0; i < N; ++i) {
- String key = in.readString();
- final int M = in.readInt();
- if (M == -1) {
- keySetMapping.put(key, null);
- continue;
- }
-
- ArraySet<PublicKey> keys = new ArraySet<>(M);
- for (int j = 0; j < M; ++j) {
- PublicKey pk = (PublicKey) in.readSerializable();
- keys.add(pk);
- }
-
- keySetMapping.put(key, keys);
- }
-
- return keySetMapping;
- }
-
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Package>() {
public Package createFromParcel(Parcel in) {
return new Package(in);
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index 9fee7bb..f2a6a5c 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -535,7 +535,7 @@
ai.setMaxAspectRatio(maxAspectRatio != null ? maxAspectRatio : 0f);
Float minAspectRatio = a.getMinAspectRatio();
ai.setMinAspectRatio(minAspectRatio != null ? minAspectRatio : 0f);
- ai.supportsSizeChanges = a.getSupportsSizeChanges();
+ ai.supportsSizeChanges = a.isSupportsSizeChanges();
ai.requestedVrComponent = a.getRequestedVrComponent();
ai.rotationAnimation = a.getRotationAnimation();
ai.colorMode = a.getColorMode();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 72cc929..d6e1ac9 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -360,7 +360,7 @@
ParsingPackage setCompileSdkVersion(int compileSdkVersion);
- ParsingPackage setCompileSdkVersionCodename(String compileSdkVersionCodename);
+ ParsingPackage setCompileSdkVersionCodeName(String compileSdkVersionCodeName);
ParsingPackage setAttributionsAreUserVisible(boolean attributionsAreUserVisible);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 0db6546..f0d95d9 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -553,7 +553,7 @@
setCompileSdkVersion(manifestArray.getInteger(
R.styleable.AndroidManifest_compileSdkVersion, 0));
- setCompileSdkVersionCodename(manifestArray.getNonConfigurationString(
+ setCompileSdkVersionCodeName(manifestArray.getNonConfigurationString(
R.styleable.AndroidManifest_compileSdkVersionCodename, 0));
setIsolatedSplitLoading(manifestArray.getBoolean(
@@ -2686,8 +2686,8 @@
}
@Override
- public ParsingPackage setCompileSdkVersionCodename(String compileSdkVersionCodename) {
- this.compileSdkVersionCodeName = compileSdkVersionCodename;
+ public ParsingPackage setCompileSdkVersionCodeName(String compileSdkVersionCodeName) {
+ this.compileSdkVersionCodeName = compileSdkVersionCodeName;
return this;
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java
index 73ee132..adb6b76 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivity.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java
@@ -23,6 +23,7 @@
import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
@@ -423,6 +424,7 @@
}
}
+ @NonNull
public static final Parcelable.Creator<ParsedActivity> CREATOR = new Creator<ParsedActivity>() {
@Override
public ParsedActivity createFromParcel(Parcel source) {
@@ -513,10 +515,6 @@
return minAspectRatio;
}
- public boolean getSupportsSizeChanges() {
- return supportsSizeChanges;
- }
-
@Nullable
public String getRequestedVrComponent() {
return requestedVrComponent;
diff --git a/core/java/android/content/pm/parsing/component/ParsedComponent.java b/core/java/android/content/pm/parsing/component/ParsedComponent.java
index 3c0f097..838adfd 100644
--- a/core/java/android/content/pm/parsing/component/ParsedComponent.java
+++ b/core/java/android/content/pm/parsing/component/ParsedComponent.java
@@ -43,8 +43,8 @@
/** @hide */
public abstract class ParsedComponent implements Parcelable {
- private static ParsedIntentInfo.ListParceler sForIntentInfos = Parcelling.Cache.getOrCreate(
- ParsedIntentInfo.ListParceler.class);
+ private static final ParsedIntentInfo.ListParceler sForIntentInfos =
+ Parcelling.Cache.getOrCreate(ParsedIntentInfo.ListParceler.class);
@NonNull
@DataClass.ParcelWith(ForInternedString.class)
diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
index 65ff472..4178920 100644
--- a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
+++ b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
@@ -18,6 +18,7 @@
import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.Parcel;
@@ -94,6 +95,7 @@
this.functionalTest = in.readByte() != 0;
}
+ @NonNull
public static final Parcelable.Creator<ParsedInstrumentation> CREATOR =
new Parcelable.Creator<ParsedInstrumentation>() {
@Override
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
index 01ee0f4..59d4a95 100644
--- a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
@@ -16,6 +16,7 @@
package android.content.pm.parsing.component;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentFilter;
import android.os.Parcel;
@@ -58,6 +59,7 @@
item.writeIntentInfoToParcel(dest, parcelFlags);
}
+ @NonNull
@Override
public ParsedIntentInfo unparcel(Parcel source) {
return new ParsedIntentInfo(source);
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index 50bc3d9..0f82941 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -16,6 +16,7 @@
package android.content.pm.parsing.component;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PermissionInfo;
import android.os.Parcel;
@@ -167,6 +168,7 @@
this.knownCerts = sForStringSet.unparcel(in);
}
+ @NonNull
public static final Parcelable.Creator<ParsedPermission> CREATOR =
new Parcelable.Creator<ParsedPermission>() {
@Override
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index c39d6b1..fe10225 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -19,7 +19,6 @@
import static java.util.Collections.emptySet;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/pm/parsing/component/ParsedProvider.java b/core/java/android/content/pm/parsing/component/ParsedProvider.java
index ebf85f7..9a12b48 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProvider.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProvider.java
@@ -169,6 +169,7 @@
this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
}
+ @NonNull
public static final Parcelable.Creator<ParsedProvider> CREATOR = new Creator<ParsedProvider>() {
@Override
public ParsedProvider createFromParcel(Parcel source) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedService.java b/core/java/android/content/pm/parsing/component/ParsedService.java
index 471d346..5499a13 100644
--- a/core/java/android/content/pm/parsing/component/ParsedService.java
+++ b/core/java/android/content/pm/parsing/component/ParsedService.java
@@ -18,6 +18,7 @@
import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.Parcel;
@@ -83,6 +84,7 @@
this.permission = sForInternedString.unparcel(in);
}
+ @NonNull
public static final Parcelable.Creator<ParsedService> CREATOR = new Creator<ParsedService>() {
@Override
public ParsedService createFromParcel(Parcel source) {
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index d5a35bc..02245e4 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -446,16 +446,12 @@
}
}
- @Override
- protected void finalize() throws Throwable {
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- }
- super.finalize();
- }
+ public void release(boolean skipCloseNotification) {
+ boolean notifyClose = false;
- public void release() {
synchronized (mInterfaceLock) {
+ mHandlerThread.quitSafely();
+
if (mSessionProcessor != null) {
try {
mSessionProcessor.deInitSession();
@@ -469,6 +465,7 @@
if (mExtensionClientId >= 0) {
CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
if (mInitialized) {
+ notifyClose = true;
CameraExtensionCharacteristics.releaseSession();
}
}
@@ -482,6 +479,16 @@
mClientRepeatingRequestSurface = null;
mClientCaptureSurface = null;
}
+
+ if (notifyClose && !skipCloseNotification) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallbacks.onClosed(
+ CameraAdvancedExtensionSessionImpl.this));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
private void notifyConfigurationFailure() {
@@ -491,7 +498,7 @@
}
}
- release();
+ release(true /*skipCloseNotification*/);
final long ident = Binder.clearCallingIdentity();
try {
@@ -507,15 +514,7 @@
android.hardware.camera2.CameraCaptureSession.StateCallback {
@Override
public void onClosed(@NonNull CameraCaptureSession session) {
- release();
-
- final long ident = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() -> mCallbacks.onClosed(
- CameraAdvancedExtensionSessionImpl.this));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ release(false /*skipCloseNotification*/);
}
@Override
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 0bf812e..fc728a2 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -697,12 +697,12 @@
}
if (mCurrentExtensionSession != null) {
- mCurrentExtensionSession.release();
+ mCurrentExtensionSession.release(false /*skipCloseNotification*/);
mCurrentExtensionSession = null;
}
if (mCurrentAdvancedExtensionSession != null) {
- mCurrentAdvancedExtensionSession.release();
+ mCurrentAdvancedExtensionSession.release(false /*skipCloseNotification*/);
mCurrentAdvancedExtensionSession = null;
}
@@ -1352,12 +1352,12 @@
}
if (mCurrentExtensionSession != null) {
- mCurrentExtensionSession.release();
+ mCurrentExtensionSession.release(true /*skipCloseNotification*/);
mCurrentExtensionSession = null;
}
if (mCurrentAdvancedExtensionSession != null) {
- mCurrentAdvancedExtensionSession.release();
+ mCurrentAdvancedExtensionSession.release(true /*skipCloseNotification*/);
mCurrentAdvancedExtensionSession = null;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 7d29a7d..ecd2491 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -630,18 +630,13 @@
new CameraExtensionUtils.HandlerExecutor(mHandler), requestHandler);
}
- @Override
- protected void finalize() throws Throwable {
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- }
- super.finalize();
- }
-
/** @hide */
- public void release() {
+ public void release(boolean skipCloseNotification) {
+ boolean notifyClose = false;
+
synchronized (mInterfaceLock) {
mInternalRepeatingRequestEnabled = false;
+ mHandlerThread.quitSafely();
try {
mPreviewExtender.onDeInit();
@@ -654,6 +649,7 @@
if (mExtensionClientId >= 0) {
CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
if (mInitialized) {
+ notifyClose = true;
CameraExtensionCharacteristics.releaseSession();
}
}
@@ -704,6 +700,15 @@
mCameraRepeatingSurface = mClientRepeatingRequestSurface = null;
mCameraBurstSurface = mClientCaptureSurface = null;
}
+
+ if (notifyClose && !skipCloseNotification) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallbacks.onClosed(CameraExtensionSessionImpl.this));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
private void notifyConfigurationFailure() {
@@ -713,7 +718,7 @@
}
}
- release();
+ release(true /*skipCloseNotification*/);
final long ident = Binder.clearCallingIdentity();
try {
@@ -745,14 +750,7 @@
android.hardware.camera2.CameraCaptureSession.StateCallback {
@Override
public void onClosed(@NonNull CameraCaptureSession session) {
- release();
-
- final long ident = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() -> mCallbacks.onClosed(CameraExtensionSessionImpl.this));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ release(false /*skipCloseNotification*/);
}
@Override
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 8fd9a6a..196134b 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1720,21 +1720,21 @@
new SetCommand() {
@Override
public <T> void setValue(CameraMetadataNative metadata, T value) {
- metadata.setAWBRegions((MeteringRectangle[]) value);
+ metadata.setAWBRegions(value);
}
});
sSetCommandMap.put(CaptureRequest.CONTROL_AF_REGIONS.getNativeKey(),
new SetCommand() {
@Override
public <T> void setValue(CameraMetadataNative metadata, T value) {
- metadata.setAFRegions((MeteringRectangle[]) value);
+ metadata.setAFRegions(value);
}
});
sSetCommandMap.put(CaptureRequest.CONTROL_AE_REGIONS.getNativeKey(),
new SetCommand() {
@Override
public <T> void setValue(CameraMetadataNative metadata, T value) {
- metadata.setAERegions((MeteringRectangle[]) value);
+ metadata.setAERegions(value);
}
});
}
@@ -1815,30 +1815,33 @@
return true;
}
- private <T> boolean setAFRegions(MeteringRectangle[] afRegions) {
+ private <T> boolean setAFRegions(T afRegions) {
if (afRegions == null) {
return false;
}
setBase(CaptureRequest.CONTROL_AF_REGIONS_SET, true);
- setBase(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
+ // The cast to CaptureRequest.Key is needed since java does not support template
+ // specialization and we need to route this method to
+ // setBase(CaptureRequest.Key<T> key, T value)
+ setBase((CaptureRequest.Key)CaptureRequest.CONTROL_AF_REGIONS, afRegions);
return true;
}
- private <T> boolean setAERegions(MeteringRectangle[] aeRegions) {
+ private <T> boolean setAERegions(T aeRegions) {
if (aeRegions == null) {
return false;
}
setBase(CaptureRequest.CONTROL_AE_REGIONS_SET, true);
- setBase(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
+ setBase((CaptureRequest.Key)CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
return true;
}
- private <T> boolean setAWBRegions(MeteringRectangle[] awbRegions) {
+ private <T> boolean setAWBRegions(T awbRegions) {
if (awbRegions == null) {
return false;
}
setBase(CaptureRequest.CONTROL_AWB_REGIONS_SET, true);
- setBase(CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
+ setBase((CaptureRequest.Key)CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
return true;
}
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index f38271a..403f55c 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -34,6 +34,7 @@
import com.android.internal.os.IDropBoxManagerService;
+import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
@@ -257,7 +258,8 @@
} else {
return null;
}
- return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+ return (mFlags & IS_GZIPPED) != 0
+ ? new GZIPInputStream(new BufferedInputStream(is)) : is;
}
public static final @android.annotation.NonNull Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3aa0bcb..2f66a39 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2092,9 +2092,7 @@
* when light idle mode restrictions are being actively applied; it will return false if the
* device is in a long-term idle mode but currently running a maintenance window where
* restrictions have been lifted.
- * @hide
*/
- @UnsupportedAppUsage
public boolean isLightDeviceIdleMode() {
try {
return mService.isLightDeviceIdleMode();
@@ -2555,9 +2553,7 @@
/**
* Intent that is broadcast when the state of {@link #isLightDeviceIdleMode()} changes.
* This broadcast is only sent to registered receivers.
- * @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED
= "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b3d60a5..570d73d 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -8588,6 +8588,15 @@
* Type: INTEGER
*/
public static final String UNGROUPED_WITH_PHONES = "summ_phones";
+
+ /**
+ * Flag indicating if the account is the default account for new contacts. At most one
+ * account has this flag set at a time. It can only be set to 1 on a row with null data set.
+ * <p>
+ * Type: INTEGER (boolean)
+ * @hide
+ */
+ String IS_DEFAULT = "x_is_default";
}
/**
@@ -8681,6 +8690,13 @@
* The MIME-type of {@link #CONTENT_URI} providing a single setting.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
+
+ /**
+ * Action used to launch the UI to set the default account for new contacts.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SET_DEFAULT_ACCOUNT =
+ "android.provider.action.SET_DEFAULT_ACCOUNT";
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f3ebe5b..ffc98d2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19816,6 +19816,9 @@
/**
* Check if this view can be scrolled horizontally in a certain direction.
*
+ * <p>This is without regard to whether the view is enabled or not, or if it will scroll
+ * in response to user input or not.
+ *
* @param direction Negative to check scrolling left, positive to check scrolling right.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*/
@@ -19833,6 +19836,9 @@
/**
* Check if this view can be scrolled vertically in a certain direction.
*
+ * <p>This is without regard to whether the view is enabled or not, or if it will scroll
+ * in response to user input or not.
+ *
* @param direction Negative to check scrolling up, positive to check scrolling down.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*/
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index ccca031..5185dc2 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -932,14 +932,20 @@
boolean requestCursorUpdates(int cursorUpdateMode);
/**
- * Called by the {@link InputMethodManager} to enable application developers to specify a
- * dedicated {@link Handler} on which incoming IPC method calls from input methods will be
- * dispatched.
+ * Called by the system to enable application developers to specify a dedicated thread on which
+ * {@link InputConnection} methods are called back.
*
- * <p>Note: This does nothing when called from input methods.</p>
+ * <p><strong>Editor authors</strong>: although you can return your custom subclasses of
+ * {@link Handler}, the system only uses {@link android.os.Looper} returned from
+ * {@link Handler#getLooper()}. You cannot intercept or cancel {@link InputConnection}
+ * callbacks by implementing this method.</p>
+ *
+ * <p><strong>IME authors</strong>: This method is not intended to be called from the IME. You
+ * will always receive {@code null}.</p>
*
* @return {@code null} to use the default {@link Handler}.
*/
+ @Nullable
Handler getHandler();
/**
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 91fc5a5..e827f0a 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5824,25 +5824,6 @@
return false;
}
- /** @hide */
- public void updateAppInfo(@NonNull ApplicationInfo info) {
- if (mApplication != null && mApplication.sourceDir.equals(info.sourceDir)) {
- // Overlay paths are generated against a particular version of an application.
- // The overlays paths of a newly upgraded application are incompatible with the
- // old version of the application.
- mApplication = info;
- }
- if (hasSizedRemoteViews()) {
- for (RemoteViews layout : mSizedRemoteViews) {
- layout.updateAppInfo(info);
- }
- }
- if (hasLandscapeAndPortraitLayouts()) {
- mLandscape.updateAppInfo(info);
- mPortrait.updateAppInfo(info);
- }
- }
-
private Context getContextForResources(Context context) {
if (mApplication != null) {
if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index b7de7b8..1c5b39e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -23,6 +23,7 @@
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -94,8 +95,16 @@
/** The container can show on top of lock screen. */
public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6;
+ /**
+ * Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
+ * used to prevent seamless rotation.
+ * TODO(b/194540864): Once we can include all windows in transition, then replace this with
+ * something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
+ */
+ public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 7;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 8;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -107,6 +116,7 @@
FLAG_IS_VOICE_INTERACTION,
FLAG_IS_DISPLAY,
FLAG_OCCLUDES_KEYGUARD,
+ FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -209,6 +219,10 @@
return mOptions;
}
+ /**
+ * @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom
+ * in Z (meaning index 0 is the top-most container).
+ */
@NonNull
public List<Change> getChanges() {
return mChanges;
@@ -290,6 +304,9 @@
if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD");
}
+ if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
+ sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
}
@@ -337,6 +354,7 @@
private ActivityManager.RunningTaskInfo mTaskInfo = null;
private int mStartRotation = ROTATION_UNDEFINED;
private int mEndRotation = ROTATION_UNDEFINED;
+ private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
mContainer = container;
@@ -356,6 +374,7 @@
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
mStartRotation = in.readInt();
mEndRotation = in.readInt();
+ mRotationAnimation = in.readInt();
}
/** Sets the parent of this change's container. The parent must be a participant or null. */
@@ -402,6 +421,14 @@
mEndRotation = end;
}
+ /**
+ * Sets the app-requested animation type for rotation. Will be one of the
+ * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams};
+ */
+ public void setRotationAnimation(int anim) {
+ mRotationAnimation = anim;
+ }
+
/** @return the container that is changing. May be null if non-remotable (eg. activity) */
@Nullable
public WindowContainerToken getContainer() {
@@ -473,6 +500,11 @@
return mEndRotation;
}
+ /** @return the rotation animation. */
+ public int getRotationAnimation() {
+ return mRotationAnimation;
+ }
+
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -487,6 +519,7 @@
dest.writeTypedObject(mTaskInfo, flags);
dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation);
+ dest.writeInt(mRotationAnimation);
}
@NonNull
@@ -514,7 +547,7 @@
return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
+ mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
- + mStartRotation + "->" + mEndRotation + "}";
+ + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}";
}
}
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 2d44054..0c27012 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -121,25 +121,54 @@
// reportFinish() will take effect.
return;
}
- closeConnection();
-
- // Notify the app that the InputConnection was closed.
- final View servedView = mServedView.get();
- if (servedView != null) {
- final Handler handler = servedView.getHandler();
- // The handler is null if the view is already detached. When that's the case, for
- // now, we simply don't dispatch this callback.
- if (handler != null) {
- if (DEBUG) {
- Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView);
+ dispatch(() -> {
+ // Note that we do not need to worry about race condition here, because 1) mFinished is
+ // updated only inside this block, and 2) the code here is running on a Handler hence we
+ // assume multiple closeConnection() tasks will not be handled at the same time.
+ if (isFinished()) {
+ return;
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection");
+ try {
+ InputConnection ic = getInputConnection();
+ // Note we do NOT check isActive() here, because this is safe
+ // for an IME to call at any time, and we need to allow it
+ // through to clean up our state after the IME has switched to
+ // another client.
+ if (ic == null) {
+ return;
}
- if (handler.getLooper().isCurrentThread()) {
- servedView.onInputConnectionClosedInternal();
- } else {
- handler.post(servedView::onInputConnectionClosedInternal);
+ @MissingMethodFlags
+ final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
+ if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
+ ic.closeConnection();
+ }
+ } finally {
+ synchronized (mLock) {
+ mInputConnection = null;
+ mFinished = true;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_INPUT);
+ }
+
+ // Notify the app that the InputConnection was closed.
+ final View servedView = mServedView.get();
+ if (servedView != null) {
+ final Handler handler = servedView.getHandler();
+ // The handler is null if the view is already detached. When that's the case, for
+ // now, we simply don't dispatch this callback.
+ if (handler != null) {
+ if (DEBUG) {
+ Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView);
+ }
+ if (handler.getLooper().isCurrentThread()) {
+ servedView.onInputConnectionClosedInternal();
+ } else {
+ handler.post(servedView::onInputConnectionClosedInternal);
+ }
}
}
- }
+ });
}
@Override
@@ -705,39 +734,6 @@
});
}
- private void closeConnection() {
- dispatch(() -> {
- // Note that we do not need to worry about race condition here, because 1) mFinished is
- // updated only inside this block, and 2) the code here is running on a Handler hence we
- // assume multiple closeConnection() tasks will not be handled at the same time.
- if (isFinished()) {
- return;
- }
- Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection");
- try {
- InputConnection ic = getInputConnection();
- // Note we do NOT check isActive() here, because this is safe
- // for an IME to call at any time, and we need to allow it
- // through to clean up our state after the IME has switched to
- // another client.
- if (ic == null) {
- return;
- }
- @MissingMethodFlags
- final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
- if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
- ic.closeConnection();
- }
- } finally {
- synchronized (mLock) {
- mInputConnection = null;
- mFinished = true;
- }
- Trace.traceEnd(Trace.TRACE_TAG_INPUT);
- }
- });
- }
-
@Override
public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts,
IBooleanResultCallback callback) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index a6d038d..8a1f1a0 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2432,6 +2432,12 @@
return (jint)nativeToJavaStatus(status);
}
+static jint android_media_AudioSystem_setHotwordDetectionServiceUid(JNIEnv *env, jobject thiz,
+ jint uid) {
+ status_t status = AudioSystem::setHotwordDetectionServiceUid(uid);
+ return (jint)nativeToJavaStatus(status);
+}
+
static jint
android_media_AudioSystem_setA11yServicesUids(JNIEnv *env, jobject thiz, jintArray uids) {
std::vector<uid_t> nativeUidsVector;
@@ -2804,6 +2810,8 @@
{"setSurroundFormatEnabled", "(IZ)I",
(void *)android_media_AudioSystem_setSurroundFormatEnabled},
{"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid},
+ {"setHotwordDetectionServiceUid", "(I)I",
+ (void *)android_media_AudioSystem_setHotwordDetectionServiceUid},
{"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
{"isHapticPlaybackSupported", "()Z",
(void *)android_media_AudioSystem_isHapticPlaybackSupported},
diff --git a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
index d667af3..d60c0c6 100644
--- a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
@@ -19,13 +19,25 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import android.annotation.DurationMillisLong;
import android.app.Instrumentation;
import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.text.InputType;
import android.text.format.DateUtils;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
@@ -45,12 +57,23 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
/**
* Tests for internal APIs/behaviors of {@link View} and {@link InputConnection}.
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
public class ViewInputConnectionTest {
+ @DurationMillisLong
+ private static final long TIMEOUT = 5000;
+ @DurationMillisLong
+ private static final long EXPECTED_TIMEOUT = 500;
+
@Rule
public ActivityTestRule<ViewInputConnectionTestActivity> mActivityRule =
new ActivityTestRule<>(ViewInputConnectionTestActivity.class);
@@ -289,4 +312,282 @@
super.onInputConnectionClosedInternal();
}
}
+
+
+ @Test
+ public void testInputConnectionCallbacks_nonUiThread() throws Throwable {
+ try (InputConnectionHandlingThread thread = new InputConnectionHandlingThread()) {
+ final ViewGroup viewGroup = getOnMainSync(() -> mActivity.findViewById(R.id.root));
+ final TestOffThreadEditor editor = getOnMainSync(() -> {
+ final TestOffThreadEditor myEditor =
+ new TestOffThreadEditor(viewGroup.getContext(), thread.getHandler());
+ viewGroup.addView(myEditor);
+ myEditor.requestFocus();
+ return myEditor;
+ });
+
+ mInstrumentation.waitForIdleSync();
+
+ assertThat(editor.mOnCreateInputConnectionCalled.await(TIMEOUT, TimeUnit.MILLISECONDS))
+ .isTrue();
+
+ // Invalidate the currently used InputConnection by moving the focus to a new EditText.
+ mActivityRule.runOnUiThread(() -> {
+ final EditText editText = new EditText(viewGroup.getContext());
+ viewGroup.addView(editText);
+ editText.requestFocus();
+ });
+
+ // Make sure that InputConnection#closeConnection() gets called on the handler thread.
+ assertThat(editor.mInputConnectionClosedCalled.await(TIMEOUT, TimeUnit.MILLISECONDS))
+ .isTrue();
+ assertThat(editor.mInputConnectionClosedCallingThreadId.get())
+ .isEqualTo(thread.getThreadId());
+
+ // Make sure that View#onInputConnectionClosed() is not yet dispatched, because
+ // InputConnection#closeConnection() is still blocked.
+ assertThat(editor.mOnInputConnectionClosedCalled.await(
+ EXPECTED_TIMEOUT, TimeUnit.MILLISECONDS)).isFalse();
+
+ // Unblock InputConnection#closeConnection()
+ editor.mInputConnectionClosedBlocker.countDown();
+
+ // Make sure that View#onInputConnectionClosed() is dispatched on the main thread.
+ assertThat(editor.mOnInputConnectionClosedCalled.await(TIMEOUT, TimeUnit.MILLISECONDS))
+ .isTrue();
+ assertThat(editor.mInputConnectionClosedBlockerTimedOut.get()).isFalse();
+ assertThat(editor.mOnInputConnectionClosedCallingThreadId.get())
+ .isEqualTo(getOnMainSync(Process::myTid));
+ }
+ }
+
+ private <T> T getOnMainSync(@NonNull Supplier<T> supplier) throws Throwable {
+ final AtomicReference<T> result = new AtomicReference<>();
+ mActivityRule.runOnUiThread(() -> result.set(supplier.get()));
+ return result.get();
+ }
+
+ private static class TestOffThreadEditor extends View {
+ private static final int TEST_VIEW_HEIGHT = 10;
+
+ public CountDownLatch mOnCreateInputConnectionCalled = new CountDownLatch(1);
+ public CountDownLatch mInputConnectionClosedCalled = new CountDownLatch(1);
+ public CountDownLatch mInputConnectionClosedBlocker = new CountDownLatch(1);
+ public AtomicBoolean mInputConnectionClosedBlockerTimedOut = new AtomicBoolean();
+ public AtomicReference<Integer> mInputConnectionClosedCallingThreadId =
+ new AtomicReference<>();
+
+ public CountDownLatch mOnInputConnectionClosedCalled = new CountDownLatch(1);
+ public AtomicReference<Integer> mOnInputConnectionClosedCallingThreadId =
+ new AtomicReference<>();
+
+ private final Handler mInputConnectionHandler;
+
+ TestOffThreadEditor(Context context, @NonNull Handler inputConnectionHandler) {
+ super(context);
+ setBackgroundColor(Color.YELLOW);
+ setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, TEST_VIEW_HEIGHT));
+ setFocusableInTouchMode(true);
+ setFocusable(true);
+ mInputConnectionHandler = inputConnectionHandler;
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ mOnCreateInputConnectionCalled.countDown();
+ outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+ return new NoOpInputConnection() {
+ @Override
+ public Handler getHandler() {
+ return mInputConnectionHandler;
+ }
+
+ @Override
+ public void closeConnection() {
+ mInputConnectionClosedCallingThreadId.compareAndSet(null, Process.myTid());
+ mInputConnectionClosedCalled.countDown();
+ try {
+ if (mInputConnectionClosedBlocker.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ return;
+ }
+ } catch (InterruptedException e) {
+ }
+ mInputConnectionClosedBlockerTimedOut.set(true);
+ }
+ };
+ }
+
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return true;
+ }
+
+ @Override
+ public void onInputConnectionClosedInternal() {
+ mOnInputConnectionClosedCallingThreadId.compareAndSet(null, Process.myTid());
+ mOnInputConnectionClosedCalled.countDown();
+ super.onInputConnectionClosedInternal();
+ }
+ }
+
+ static class NoOpInputConnection implements InputConnection {
+
+ @Override
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getSelectedText(int flags) {
+ return null;
+ }
+
+ @Override
+ public int getCursorCapsMode(int reqModes) {
+ return 0;
+ }
+
+ @Override
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ return null;
+ }
+
+ @Override
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ return false;
+ }
+
+ @Override
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ return false;
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ return false;
+ }
+
+ @Override
+ public boolean setComposingRegion(int start, int end) {
+ return false;
+ }
+
+ @Override
+ public boolean finishComposingText() {
+ return false;
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ return false;
+ }
+
+ @Override
+ public boolean commitCompletion(CompletionInfo text) {
+ return false;
+ }
+
+ @Override
+ public boolean commitCorrection(CorrectionInfo correctionInfo) {
+ return false;
+ }
+
+ @Override
+ public boolean setSelection(int start, int end) {
+ return false;
+ }
+
+ @Override
+ public boolean performEditorAction(int editorAction) {
+ return false;
+ }
+
+ @Override
+ public boolean performContextMenuAction(int id) {
+ return false;
+ }
+
+ @Override
+ public boolean beginBatchEdit() {
+ return false;
+ }
+
+ @Override
+ public boolean endBatchEdit() {
+ return false;
+ }
+
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean clearMetaKeyStates(int states) {
+ return false;
+ }
+
+ @Override
+ public boolean reportFullscreenMode(boolean enabled) {
+ return false;
+ }
+
+ @Override
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return false;
+ }
+
+ @Override
+ public boolean requestCursorUpdates(int cursorUpdateMode) {
+ return false;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return null;
+ }
+
+ @Override
+ public void closeConnection() {
+
+ }
+
+ @Override
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+ return false;
+ }
+ }
+
+ private static final class InputConnectionHandlingThread extends HandlerThread
+ implements AutoCloseable {
+
+ private final Handler mHandler;
+
+ InputConnectionHandlingThread() {
+ super("IC-callback");
+ start();
+ mHandler = Handler.createAsync(getLooper());
+ }
+
+ @NonNull
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void close() {
+ quitSafely();
+ try {
+ join(TIMEOUT);
+ } catch (InterruptedException e) {
+ fail("Failed to stop the thread: " + e);
+ }
+ }
+ }
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index d3cff5c..7354c90 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -49,7 +49,9 @@
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.Build;
+import android.os.Looper;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
@@ -113,6 +115,7 @@
* @attr ref android.R.styleable#RippleDrawable_color
*/
public class RippleDrawable extends LayerDrawable {
+ private static final String TAG = "RippleDrawable";
/**
* Radius value that specifies the ripple radius should be computed based
* on the size of the ripple's container.
@@ -848,6 +851,10 @@
private void startBackgroundAnimation() {
mRunBackgroundAnimation = false;
+ if (Looper.myLooper() == null) {
+ Log.w(TAG, "Thread doesn't have a looper. Skipping animation.");
+ return;
+ }
mBackgroundAnimation = ValueAnimator.ofFloat(mBackgroundOpacity, mTargetBackgroundOpacity);
mBackgroundAnimation.setInterpolator(LINEAR_INTERPOLATOR);
mBackgroundAnimation.setDuration(BACKGROUND_OPACITY_DURATION);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index d1fbf31..9bbede3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -24,6 +24,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -47,6 +48,7 @@
private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
+ private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
private final StartingWindowController mStartingWindow;
@@ -62,6 +64,7 @@
Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional,
Transitions transitions,
StartingWindowController startingWindow,
ShellExecutor mainExecutor) {
@@ -74,6 +77,7 @@
mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional;
+ mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f);
mTransitions = transitions;
mMainExecutor = mainExecutor;
mStartingWindow = startingWindow;
@@ -108,6 +112,11 @@
// controller instead of the feature interface, can just initialize the touch handler if
// needed
mPipTouchHandlerOptional.ifPresent((handler) -> handler.init());
+
+ // Initialize optional freeform
+ mFreeformTaskListenerOptional.ifPresent(f ->
+ mShellTaskOrganizer.addListenerForType(
+ f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
}
@ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ba0ab6d..ab8a21c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -71,12 +71,14 @@
public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
public static final int TASK_LISTENER_TYPE_PIP = -4;
+ public static final int TASK_LISTENER_TYPE_FREEFORM = -5;
@IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
TASK_LISTENER_TYPE_UNDEFINED,
TASK_LISTENER_TYPE_FULLSCREEN,
TASK_LISTENER_TYPE_MULTI_WINDOW,
TASK_LISTENER_TYPE_PIP,
+ TASK_LISTENER_TYPE_FREEFORM,
})
public @interface TaskListenerType {}
@@ -572,6 +574,7 @@
case WINDOWING_MODE_PINNED:
return TASK_LISTENER_TYPE_PIP;
case WINDOWING_MODE_FREEFORM:
+ return TASK_LISTENER_TYPE_FREEFORM;
case WINDOWING_MODE_UNDEFINED:
default:
return TASK_LISTENER_TYPE_UNDEFINED;
@@ -586,6 +589,8 @@
return "TASK_LISTENER_TYPE_MULTI_WINDOW";
case TASK_LISTENER_TYPE_PIP:
return "TASK_LISTENER_TYPE_PIP";
+ case TASK_LISTENER_TYPE_FREEFORM:
+ return "TASK_LISTENER_TYPE_FREEFORM";
case TASK_LISTENER_TYPE_UNDEFINED:
return "TASK_LISTENER_TYPE_UNDEFINED";
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index b7235a3..b90283f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -82,6 +82,9 @@
private boolean mHasNavigationBar = false;
private boolean mHasStatusBar = false;
private int mNavBarFrameHeight = 0;
+ private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
+ private boolean mNavigationBarCanMove = false;
+ private boolean mReverseDefaultRotation = false;
@Override
public boolean equals(Object o) {
@@ -98,6 +101,10 @@
&& Objects.equals(mStableInsets, other.mStableInsets)
&& mHasNavigationBar == other.mHasNavigationBar
&& mHasStatusBar == other.mHasStatusBar
+ && mAllowSeamlessRotationDespiteNavBarMoving
+ == other.mAllowSeamlessRotationDespiteNavBarMoving
+ && mNavigationBarCanMove == other.mNavigationBarCanMove
+ && mReverseDefaultRotation == other.mReverseDefaultRotation
&& mNavBarFrameHeight == other.mNavBarFrameHeight;
}
@@ -105,7 +112,8 @@
public int hashCode() {
return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
- mNavBarFrameHeight);
+ mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
+ mNavigationBarCanMove, mReverseDefaultRotation);
}
/**
@@ -150,6 +158,9 @@
mDensityDpi = dl.mDensityDpi;
mHasNavigationBar = dl.mHasNavigationBar;
mHasStatusBar = dl.mHasStatusBar;
+ mAllowSeamlessRotationDespiteNavBarMoving = dl.mAllowSeamlessRotationDespiteNavBarMoving;
+ mNavigationBarCanMove = dl.mNavigationBarCanMove;
+ mReverseDefaultRotation = dl.mReverseDefaultRotation;
mNavBarFrameHeight = dl.mNavBarFrameHeight;
mNonDecorInsets.set(dl.mNonDecorInsets);
mStableInsets.set(dl.mStableInsets);
@@ -165,6 +176,10 @@
mDensityDpi = info.logicalDensityDpi;
mHasNavigationBar = hasNavigationBar;
mHasStatusBar = hasStatusBar;
+ mAllowSeamlessRotationDespiteNavBarMoving = res.getBoolean(
+ R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
+ mNavigationBarCanMove = res.getBoolean(R.bool.config_navBarCanMove);
+ mReverseDefaultRotation = res.getBoolean(R.bool.config_reverseDefaultRotation);
recalcInsets(res);
}
@@ -249,6 +264,28 @@
return mNavBarFrameHeight;
}
+ /** @return whether we can seamlessly rotate even if nav-bar can change sides. */
+ public boolean allowSeamlessRotationDespiteNavBarMoving() {
+ return mAllowSeamlessRotationDespiteNavBarMoving;
+ }
+
+ /** @return whether the navigation bar will change sides during rotation. */
+ public boolean navigationBarCanMove() {
+ return mNavigationBarCanMove;
+ }
+
+ /** @return the rotation that would make the physical display "upside down". */
+ public int getUpsideDownRotation() {
+ boolean displayHardwareIsLandscape = mWidth > mHeight;
+ if ((mRotation % 2) != 0) {
+ displayHardwareIsLandscape = !displayHardwareIsLandscape;
+ }
+ if (displayHardwareIsLandscape) {
+ return mReverseDefaultRotation ? Surface.ROTATION_270 : Surface.ROTATION_90;
+ }
+ return Surface.ROTATION_180;
+ }
+
/** Gets the orientation of this layout */
public int getOrientation() {
return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
new file mode 100644
index 0000000..5fb3297
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.freeform;
+
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+
+/**
+ * {@link ShellTaskOrganizer.TaskListener} for {@link
+ * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
+ */
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = "FreeformTaskListener";
+
+ private final SyncTransactionQueue mSyncQueue;
+
+ private final SparseArray<State> mTasks = new SparseArray<>();
+
+ private static class State {
+ RunningTaskInfo mTaskInfo;
+ SurfaceControl mLeash;
+ }
+
+ public FreeformTaskListener(SyncTransactionQueue syncQueue) {
+ mSyncQueue = syncQueue;
+ }
+
+ @Override
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mTasks.get(taskInfo.taskId) != null) {
+ throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
+ taskInfo.taskId);
+ final State state = new State();
+ state.mTaskInfo = taskInfo;
+ state.mLeash = leash;
+ mTasks.put(taskInfo.taskId, state);
+
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ mSyncQueue.runInSync(t -> {
+ Point taskPosition = taskInfo.positionInParent;
+ t.setPosition(leash, taskPosition.x, taskPosition.y)
+ .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
+ .show(leash);
+ });
+ }
+
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ State state = mTasks.get(taskInfo.taskId);
+ if (state == null) {
+ Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+ return;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
+ taskInfo.taskId);
+ mTasks.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ State state = mTasks.get(taskInfo.taskId);
+ if (state == null) {
+ throw new RuntimeException(
+ "Task info changed before appearing: #" + taskInfo.taskId);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
+ taskInfo.taskId);
+ state.mTaskInfo = taskInfo;
+
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final SurfaceControl leash = state.mLeash;
+ mSyncQueue.runInSync(t -> {
+ Point taskPosition = taskInfo.positionInParent;
+ t.setPosition(leash, taskPosition.x, taskPosition.y)
+ .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
+ .show(leash);
+ });
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + this);
+ pw.println(innerPrefix + mTasks.size() + " tasks");
+ }
+
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ /**
+ * Checks if freeform support is enabled in system.
+ *
+ * @param context context used to check settings and package manager.
+ * @return {@code true} if freeform is enabled, {@code false} if not.
+ */
+ public static boolean isFreeformEnabled(Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
+ || Settings.Global.getInt(context.getContentResolver(),
+ DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
+ }
+
+ /**
+ * Creates {@link FreeformTaskListener} if freeform is enabled.
+ */
+ public static FreeformTaskListener create(Context context,
+ SyncTransactionQueue syncQueue) {
+ if (!isFreeformEnabled(context)) {
+ return null;
+ }
+
+ return new FreeformTaskListener(syncQueue);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 21bc889..ff333c8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -130,7 +130,7 @@
*/
public boolean getSettingsTapsAppToExit(ContentResolver resolver, int userId) {
return Settings.Secure.getIntForUser(resolver,
- Settings.Secure.TAPS_APP_TO_EXIT, 0, userId) == 1;
+ Settings.Secure.TAPS_APP_TO_EXIT, 1, userId) == 1;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 01134a7..b584038 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -23,6 +23,10 @@
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
@@ -31,8 +35,10 @@
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -68,9 +74,12 @@
import android.window.WindowContainerTransaction;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -97,6 +106,7 @@
SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
private final TransactionPool mTransactionPool;
+ private final DisplayController mDisplayController;
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
@@ -114,8 +124,10 @@
private ScreenRotationAnimation mRotationAnimation;
- DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
+ DefaultTransitionHandler(@NonNull DisplayController displayController,
+ @NonNull TransactionPool transactionPool, Context context,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
mMainExecutor = mainExecutor;
@@ -126,6 +138,110 @@
AttributeCache.init(context);
}
+ @VisibleForTesting
+ static boolean isRotationSeamless(@NonNull TransitionInfo info,
+ DisplayController displayController) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Display is rotating, check if it should be seamless.");
+ boolean checkedDisplayLayout = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+
+ // Only look at changing things. showing/hiding don't need to rotate.
+ if (change.getMode() != TRANSIT_CHANGE) continue;
+
+ // This container isn't rotating, so we can ignore it.
+ if (change.getEndRotation() == change.getStartRotation()) continue;
+
+ if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+ // In the presence of System Alert windows we can not seamlessly rotate.
+ if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " display has system alert windows, so not seamless.");
+ return false;
+ }
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " wallpaper is participating but isn't seamless.");
+ return false;
+ }
+ } else if (change.getTaskInfo() != null) {
+ // We only enable seamless rotation if all the visible task windows requested it.
+ if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " task %s isn't requesting seamless, so not seamless.",
+ change.getTaskInfo().taskId);
+ return false;
+ }
+
+ // This is the only way to get display-id currently, so we will check display
+ // capabilities here
+ if (!checkedDisplayLayout) {
+ // only need to check display once.
+ checkedDisplayLayout = true;
+ final DisplayLayout displayLayout = displayController.getDisplayLayout(
+ change.getTaskInfo().displayId);
+ // For the upside down rotation we don't rotate seamlessly as the navigation
+ // bar moves position. Note most apps (using orientation:sensor or user as
+ // opposed to fullSensor) will not enter the reverse portrait orientation, so
+ // actually the orientation won't change at all.
+ int upsideDownRotation = displayLayout.getUpsideDownRotation();
+ if (change.getStartRotation() == upsideDownRotation
+ || change.getEndRotation() == upsideDownRotation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " rotation involves upside-down portrait, so not seamless.");
+ return false;
+ }
+
+ // If the navigation bar can't change sides, then it will jump when we change
+ // orientations and we don't rotate seamlessly - unless that is allowed, eg.
+ // with gesture navigation where the navbar is low-profile enough that this
+ // isn't very noticeable.
+ if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
+ && (!(displayLayout.navigationBarCanMove()
+ && (change.getStartAbsBounds().width()
+ != change.getStartAbsBounds().height())))) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " nav bar changes sides, so not seamless.");
+ return false;
+ }
+ }
+ }
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
+ return true;
+ }
+
+ /**
+ * Gets the rotation animation for the topmost task. Assumes that seamless is checked
+ * elsewhere, so it will default SEAMLESS to ROTATE.
+ */
+ private int getRotationAnimation(@NonNull TransitionInfo info) {
+ // Traverse in top-to-bottom order so that the first task is top-most
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+
+ // Only look at changing things. showing/hiding don't need to rotate.
+ if (change.getMode() != TRANSIT_CHANGE) continue;
+
+ // This container isn't rotating, so we can ignore it.
+ if (change.getEndRotation() == change.getStartRotation()) continue;
+
+ if (change.getTaskInfo() != null) {
+ final int anim = change.getRotationAnimation();
+ if (anim == ROTATION_ANIMATION_UNSPECIFIED
+ // Fallback animation for seamless should also be default.
+ || anim == ROTATION_ANIMATION_SEAMLESS) {
+ return ROTATION_ANIMATION_ROTATE;
+ }
+ return anim;
+ }
+ }
+ return ROTATION_ANIMATION_ROTATE;
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -168,11 +284,15 @@
if (info.getType() == TRANSIT_CHANGE && change.getMode() == TRANSIT_CHANGE
&& (change.getEndRotation() != change.getStartRotation())
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
- mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
- mTransactionPool, startTransaction, change, info.getRootLeash());
- mRotationAnimation.startAnimation(animations, onAnimFinish,
- mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
- continue;
+ boolean isSeamless = isRotationSeamless(info, mDisplayController);
+ final int anim = getRotationAnimation(info);
+ if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
+ mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
+ mTransactionPool, startTransaction, change, info.getRootLeash());
+ mRotationAnimation.startAnimation(animations, onAnimFinish,
+ mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
+ continue;
+ }
}
if (change.getMode() == TRANSIT_CHANGE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 01ef2a6..09dfabf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -54,6 +54,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -113,15 +114,16 @@
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
- @NonNull Context context, @NonNull ShellExecutor mainExecutor,
- @NonNull ShellExecutor animExecutor) {
+ @NonNull DisplayController displayController, @NonNull Context context,
+ @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one.
- mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
+ mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
+ animExecutor));
// Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 21329c7..84565e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -20,12 +20,18 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -49,6 +55,9 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.view.IDisplayWindowListener;
+import android.view.IWindowManager;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
@@ -66,12 +75,15 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
@@ -98,8 +110,7 @@
@Test
public void testBasicTransitionFlow() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken = new Binder();
@@ -118,8 +129,7 @@
@Test
public void testNonDefaultHandler() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
@@ -202,8 +212,7 @@
@Test
public void testRequestRemoteTransition() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
@@ -297,8 +306,7 @@
@Test
public void testRegisteredRemoteTransition() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
@@ -343,8 +351,7 @@
@Test
public void testOneShotRemoteHandler() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
@@ -390,8 +397,7 @@
@Test
public void testTransitionQueueing() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken1 = new Binder();
@@ -431,8 +437,7 @@
@Test
public void testTransitionMerging() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
mDefaultHandler.setSimulateMerge(true);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -468,6 +473,63 @@
assertEquals(0, mDefaultHandler.activeCount());
}
+ @Test
+ public void testShouldRotateSeamlessly() throws Exception {
+ final RunningTaskInfo taskInfo =
+ createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ final RunningTaskInfo taskInfoPip =
+ createTaskInfo(1, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+ final DisplayController displays = createTestDisplayController();
+ final @Surface.Rotation int upsideDown = displays
+ .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
+
+ final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+ .build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays));
+
+ // Seamless if all tasks are seamless
+ final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+ .build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays));
+
+ // Not seamless if there is PiP (or any other non-seamless task)
+ final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+ .build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip)
+ .setRotate().build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays));
+
+ // Not seamless if one of rotations is upside-down
+ final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
+
+ // Not seamless if system alert windows
+ final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
+ FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays));
+ }
+
class TransitionInfoBuilder {
final TransitionInfo mInfo;
@@ -490,11 +552,53 @@
return addChange(mode, null /* taskInfo */);
}
+ TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+ mInfo.addChange(change);
+ return this;
+ }
+
TransitionInfo build() {
return mInfo;
}
}
+ class ChangeBuilder {
+ final TransitionInfo.Change mChange;
+
+ ChangeBuilder(@WindowManager.TransitionType int mode) {
+ mChange = new TransitionInfo.Change(null /* token */, null /* leash */);
+ mChange.setMode(mode);
+ }
+
+ ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
+ mChange.setFlags(flags);
+ return this;
+ }
+
+ ChangeBuilder setTask(RunningTaskInfo taskInfo) {
+ mChange.setTaskInfo(taskInfo);
+ return this;
+ }
+
+ ChangeBuilder setRotate(int anim) {
+ return setRotate(Surface.ROTATION_90, anim);
+ }
+
+ ChangeBuilder setRotate() {
+ return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
+ }
+
+ ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
+ mChange.setRotation(Surface.ROTATION_0, target);
+ mChange.setRotationAnimation(anim);
+ return this;
+ }
+
+ TransitionInfo.Change build() {
+ return mChange;
+ }
+ }
+
class TestTransitionHandler implements Transitions.TransitionHandler {
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
@@ -566,4 +670,45 @@
return taskInfo;
}
+ private DisplayController createTestDisplayController() {
+ IWindowManager mockWM = mock(IWindowManager.class);
+ final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
+ try {
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ displayListener[0] = invocation.getArgument(0);
+ return null;
+ }
+ }).when(mockWM).registerDisplayWindowListener(any());
+ } catch (RemoteException e) {
+ // No remote stuff happening, so this can't be hit
+ }
+ DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor);
+ try {
+ displayListener[0].onDisplayAdded(DEFAULT_DISPLAY);
+ mMainExecutor.flushAll();
+ } catch (RemoteException e) {
+ // Again, no remote stuff
+ }
+ return out;
+ }
+
+ private Transitions createTestTransitions() {
+ return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
+ mContext, mMainExecutor, mAnimExecutor);
+ }
+//
+// private class TestDisplayController extends DisplayController {
+// private final DisplayLayout mTestDisplayLayout;
+// TestDisplayController() {
+// super(mContext, mock(IWindowManager.class), mMainExecutor);
+// mTestDisplayLayout = new DisplayLayout();
+// mTestDisplayLayout.
+// }
+//
+// @Override
+// DisplayLayout
+// }
+
}
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 6cb53206..8d112d1 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -21,6 +21,7 @@
// TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead.
#include <surfacetexture/surface_texture_platform.h>
#include "AutoBackendTextureRelease.h"
+#include "Matrix.h"
#include "Properties.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
@@ -144,17 +145,16 @@
}
if (mUpdateTexImage) {
mUpdateTexImage = false;
+ float transformMatrix[16];
android_dataspace dataspace;
int slot;
bool newContent = false;
- ARect rect;
- uint32_t textureTransform;
// Note: ASurfaceTexture_dequeueBuffer discards all but the last frame. This
// is necessary if the SurfaceTexture queue is in synchronous mode, and we
// cannot tell which mode it is in.
AHardwareBuffer* hardwareBuffer = ASurfaceTexture_dequeueBuffer(
- mSurfaceTexture.get(), &slot, &dataspace, &newContent, createReleaseFence,
- fenceWait, this, &rect, &textureTransform);
+ mSurfaceTexture.get(), &slot, &dataspace, transformMatrix, &newContent,
+ createReleaseFence, fenceWait, this);
if (hardwareBuffer) {
mCurrentSlot = slot;
@@ -165,12 +165,12 @@
// (invoked by createIfNeeded) will add a ref to the AHardwareBuffer.
AHardwareBuffer_release(hardwareBuffer);
if (layerImage.get()) {
+ SkMatrix textureTransform;
+ mat4(transformMatrix).copyTo(textureTransform);
// force filtration if buffer size != layer size
bool forceFilter =
mWidth != layerImage->width() || mHeight != layerImage->height();
- SkRect cropRect =
- SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
- updateLayer(forceFilter, textureTransform, cropRect, layerImage);
+ updateLayer(forceFilter, textureTransform, layerImage);
}
}
}
@@ -182,13 +182,12 @@
}
}
-void DeferredLayerUpdater::updateLayer(bool forceFilter, const uint32_t textureTransform,
- const SkRect cropRect, const sk_sp<SkImage>& layerImage) {
+void DeferredLayerUpdater::updateLayer(bool forceFilter, const SkMatrix& textureTransform,
+ const sk_sp<SkImage>& layerImage) {
mLayer->setBlend(mBlend);
mLayer->setForceFilter(forceFilter);
mLayer->setSize(mWidth, mHeight);
- mLayer->setTextureTransform(textureTransform);
- mLayer->setCropRect(cropRect);
+ mLayer->getTexTransform() = textureTransform;
mLayer->setImage(layerImage);
}
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 7a63ccd..8f79c4e 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -90,7 +90,7 @@
void detachSurfaceTexture();
- void updateLayer(bool forceFilter, const uint32_t textureTransform, const SkRect cropRect,
+ void updateLayer(bool forceFilter, const SkMatrix& textureTransform,
const sk_sp<SkImage>& layerImage);
void destroyLayer();
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 9053c12..47c47e0 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -35,6 +35,7 @@
// preserves the old inc/dec ref locations. This should be changed...
incStrong(nullptr);
renderState.registerLayer(this);
+ texTransform.setIdentity();
transform.setIdentity();
}
@@ -100,6 +101,7 @@
const int layerHeight = getHeight();
if (layerImage) {
SkMatrix textureMatrixInv;
+ textureMatrixInv = getTexTransform();
// TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
// use bottom left origin and remove flipV and invert transformations.
SkMatrix flipV;
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 656a817..e99e762 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -74,18 +74,10 @@
void setColorFilter(sk_sp<SkColorFilter> filter) { mColorFilter = filter; };
+ inline SkMatrix& getTexTransform() { return texTransform; }
+
inline SkMatrix& getTransform() { return transform; }
- inline SkRect getCropRect() { return mCropRect; }
-
- inline void setCropRect(const SkRect cropRect) { mCropRect = cropRect; }
-
- inline void setTextureTransform(uint32_t textureTransform) {
- mTextureTransform = textureTransform;
- }
-
- inline uint32_t getTextureTransform() { return mTextureTransform; }
-
/**
* Posts a decStrong call to the appropriate thread.
* Thread-safe.
@@ -124,21 +116,16 @@
SkBlendMode mode;
/**
+ * Optional texture coordinates transform.
+ */
+ SkMatrix texTransform;
+
+ /**
* Optional transform.
*/
SkMatrix transform;
/**
- * Optional crop
- */
- SkRect mCropRect;
-
- /**
- * Optional transform
- */
- uint32_t mTextureTransform;
-
- /**
* An image backing the layer.
*/
sk_sp<SkImage> layerImage;
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 386c88a..a743d30 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -251,6 +251,8 @@
Rect srcRect;
Matrix4 transform;
+ transform.loadScale(1, -1, 1);
+ transform.translate(0, -1);
return copyImageInto(hwBitmap->makeImage(), transform, srcRect, bitmap);
}
@@ -278,6 +280,8 @@
CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
Rect srcRect;
Matrix4 transform;
+ transform.loadScale(1, -1, 1);
+ transform.translate(0, -1);
return copyImageInto(image, transform, srcRect, bitmap);
}
@@ -316,6 +320,7 @@
Layer layer(mRenderThread.renderState(), nullptr, 255, SkBlendMode::kSrc);
layer.setSize(displayedWidth, displayedHeight);
+ texTransform.copyTo(layer.getTexTransform());
layer.setImage(image);
// Scaling filter is not explicitly set here, because it is done inside copyLayerInfo
// after checking the necessity based on the src/dest rect size and the transformation.
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index b28277c..e32788c 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -18,11 +18,9 @@
#include <utils/MathUtils.h>
#include "GrBackendSurface.h"
-#include "Matrix.h"
#include "SkColorFilter.h"
#include "SkSurface.h"
#include "gl/GrGLTypes.h"
-#include "system/window.h"
namespace android {
namespace uirenderer {
@@ -31,8 +29,7 @@
void LayerDrawable::onDraw(SkCanvas* canvas) {
Layer* layer = mLayerUpdater->backingLayer();
if (layer) {
- SkRect srcRect = layer->getCropRect();
- DrawLayer(canvas->recordingContext(), canvas, layer, &srcRect, nullptr, true);
+ DrawLayer(canvas->recordingContext(), canvas, layer, nullptr, nullptr, true);
}
}
@@ -82,16 +79,33 @@
return false;
}
// transform the matrix based on the layer
- const uint32_t transform = layer->getTextureTransform();
+ SkMatrix layerTransform = layer->getTransform();
sk_sp<SkImage> layerImage = layer->getImage();
const int layerWidth = layer->getWidth();
const int layerHeight = layer->getHeight();
- SkMatrix layerTransform = layer->getTransform();
+
if (layerImage) {
+ SkMatrix textureMatrixInv;
+ textureMatrixInv = layer->getTexTransform();
+ // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
+ // use bottom left origin and remove flipV and invert transformations.
+ SkMatrix flipV;
+ flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
+ textureMatrixInv.preConcat(flipV);
+ textureMatrixInv.preScale(1.0f / layerWidth, 1.0f / layerHeight);
+ textureMatrixInv.postScale(layerImage->width(), layerImage->height());
+ SkMatrix textureMatrix;
+ if (!textureMatrixInv.invert(&textureMatrix)) {
+ textureMatrix = textureMatrixInv;
+ }
+
SkMatrix matrix;
if (useLayerTransform) {
- matrix = layerTransform;
+ matrix = SkMatrix::Concat(layerTransform, textureMatrix);
+ } else {
+ matrix = textureMatrix;
}
+
SkPaint paint;
paint.setAlpha(layer->getAlpha());
paint.setBlendMode(layer->getMode());
@@ -101,54 +115,39 @@
canvas->save();
canvas->concat(matrix);
}
- const SkMatrix totalMatrix = canvas->getTotalMatrix();
+ const SkMatrix& totalMatrix = canvas->getTotalMatrix();
if (dstRect || srcRect) {
SkMatrix matrixInv;
if (!matrix.invert(&matrixInv)) {
matrixInv = matrix;
}
SkRect skiaSrcRect;
- if (srcRect && !srcRect->isEmpty()) {
+ if (srcRect) {
skiaSrcRect = *srcRect;
} else {
- skiaSrcRect = (transform & NATIVE_WINDOW_TRANSFORM_ROT_90)
- ? SkRect::MakeIWH(layerHeight, layerWidth)
- : SkRect::MakeIWH(layerWidth, layerHeight);
+ skiaSrcRect = SkRect::MakeIWH(layerWidth, layerHeight);
}
matrixInv.mapRect(&skiaSrcRect);
SkRect skiaDestRect;
- if (dstRect && !dstRect->isEmpty()) {
+ if (dstRect) {
skiaDestRect = *dstRect;
} else {
skiaDestRect = SkRect::MakeIWH(layerWidth, layerHeight);
}
matrixInv.mapRect(&skiaDestRect);
+ // If (matrix is a rect-to-rect transform)
+ // and (src/dst buffers size match in screen coordinates)
+ // and (src/dst corners align fractionally),
+ // then use nearest neighbor, otherwise use bilerp sampling.
+ // Skia TextureOp has the above logic build-in, but not NonAAFillRectOp. TextureOp works
+ // only for SrcOver blending and without color filter (readback uses Src blending).
SkSamplingOptions sampling(SkFilterMode::kNearest);
if (layer->getForceFilter() ||
shouldFilterRect(totalMatrix, skiaSrcRect, skiaDestRect)) {
sampling = SkSamplingOptions(SkFilterMode::kLinear);
}
-
- const float px = skiaDestRect.centerX();
- const float py = skiaDestRect.centerY();
- if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
- matrix.postScale(-1.f, 1.f, px, py);
- }
- if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
- matrix.postScale(1.f, -1.f, px, py);
- }
- if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
- matrix.postRotate(90, 0, 0);
- matrix.postTranslate(skiaDestRect.height(), 0);
- }
- auto constraint = SkCanvas::kFast_SrcRectConstraint;
- if (srcRect && srcRect->isEmpty()) {
- constraint = SkCanvas::kStrict_SrcRectConstraint;
- }
- matrix.postConcat(SkMatrix::MakeRectToRect(skiaSrcRect, skiaDestRect,
- SkMatrix::kFill_ScaleToFit));
canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
- constraint);
+ SkCanvas::kFast_SrcRectConstraint);
} else {
SkRect imageRect = SkRect::MakeIWH(layerImage->width(), layerImage->height());
SkSamplingOptions sampling(SkFilterMode::kNearest);
@@ -162,6 +161,7 @@
canvas->restore();
}
}
+
return layerImage != nullptr;
}
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 1bfdc47..e8ba15f 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -74,7 +74,7 @@
layerUpdater->setTransform(&transform);
// updateLayer so it's ready to draw
- layerUpdater->updateLayer(true, 0, SkRect::MakeEmpty(), nullptr);
+ layerUpdater->updateLayer(true, SkMatrix::I(), nullptr);
return layerUpdater;
}
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index ca84aee..955a5e7 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -36,20 +36,19 @@
EXPECT_EQ(0u, layerUpdater->backingLayer()->getHeight());
EXPECT_FALSE(layerUpdater->backingLayer()->getForceFilter());
EXPECT_FALSE(layerUpdater->backingLayer()->isBlend());
+ EXPECT_EQ(Matrix4::identity(), layerUpdater->backingLayer()->getTexTransform());
// push the deferred updates to the layer
- uint32_t textureTransform = 1;
+ SkMatrix scaledMatrix = SkMatrix::Scale(0.5, 0.5);
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
- SkRect cropRect = SkRect::MakeIWH(10, 10);
sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
- layerUpdater->updateLayer(true, textureTransform, cropRect, layerImage);
+ layerUpdater->updateLayer(true, scaledMatrix, layerImage);
// the backing layer should now have all the properties applied.
EXPECT_EQ(100u, layerUpdater->backingLayer()->getWidth());
EXPECT_EQ(100u, layerUpdater->backingLayer()->getHeight());
EXPECT_TRUE(layerUpdater->backingLayer()->getForceFilter());
EXPECT_TRUE(layerUpdater->backingLayer()->isBlend());
- EXPECT_EQ(textureTransform, layerUpdater->backingLayer()->getTextureTransform());
- EXPECT_EQ(cropRect, layerUpdater->backingLayer()->getCropRect());
+ EXPECT_EQ(scaledMatrix, layerUpdater->backingLayer()->getTexTransform());
}
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index c827932..cb887f2 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -38,6 +38,17 @@
public abstract void updateRingerModeAffectedStreamsInternal();
+ /**
+ * Notify the UID of the currently active {@link android.service.voice.HotwordDetectionService}.
+ *
+ * <p>The caller is expected to take care of any performance implications, e.g. by using a
+ * background thread to call this method.</p>
+ *
+ * @param uid UID of the currently active service or {@link android.os.Process#INVALID_UID} if
+ * none.
+ */
+ public abstract void setHotwordDetectionServiceUid(int uid);
+
public abstract void setAccessibilityServiceUids(IntArray uids);
/**
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 321db4e..5bd6891 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1769,6 +1769,13 @@
public static native int setAssistantUid(int uid);
/**
+ * Communicate UID of the current {@link android.service.voice.HotwordDetectionService} to audio
+ * policy service.
+ * @hide
+ */
+ public static native int setHotwordDetectionServiceUid(int uid);
+
+ /**
* @hide
* Communicate UIDs of active accessibility services to audio policy service.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 79446e4..ed8d524 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -44,6 +44,8 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -56,6 +58,7 @@
/**
* InfoMediaManager provide interface to get InfoMediaDevice list.
*/
+@RequiresApi(Build.VERSION_CODES.R)
public class InfoMediaManager extends MediaManager {
private static final String TAG = "InfoMediaManager";
@@ -145,9 +148,16 @@
}
private RoutingSessionInfo getRoutingSessionInfo() {
- final List<RoutingSessionInfo> sessionInfos =
- mRouterManager.getRoutingSessions(mPackageName);
+ return getRoutingSessionInfo(mPackageName);
+ }
+ private RoutingSessionInfo getRoutingSessionInfo(String packageName) {
+ final List<RoutingSessionInfo> sessionInfos =
+ mRouterManager.getRoutingSessions(packageName);
+
+ if (sessionInfos == null || sessionInfos.isEmpty()) {
+ return null;
+ }
return sessionInfos.get(sessionInfos.size() - 1);
}
@@ -367,33 +377,13 @@
}
boolean shouldDisableMediaOutput(String packageName) {
- boolean shouldDisableMediaOutput = false;
if (TextUtils.isEmpty(packageName)) {
Log.w(TAG, "shouldDisableMediaOutput() package name is null or empty!");
- return false;
+ return true;
}
- final List<MediaRoute2Info> infos = mRouterManager.getTransferableRoutes(packageName);
- if (infos.size() == 1) {
- final MediaRoute2Info info = infos.get(0);
- final int deviceType = info.getType();
- switch (deviceType) {
- case TYPE_UNKNOWN:
- case TYPE_REMOTE_TV:
- case TYPE_REMOTE_SPEAKER:
- case TYPE_GROUP:
- shouldDisableMediaOutput = true;
- break;
- default:
- shouldDisableMediaOutput = false;
- break;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "shouldDisableMediaOutput() MediaRoute2Info size : " + infos.size()
- + ", package name : " + packageName + ", shouldDisableMediaOutput : "
- + shouldDisableMediaOutput);
- }
- return shouldDisableMediaOutput;
+
+ // Disable when there is no transferable route
+ return mRouterManager.getTransferableRoutes(packageName).isEmpty();
}
@TargetApi(Build.VERSION_CODES.R)
@@ -456,7 +446,7 @@
}
private void buildAvailableRoutes() {
- for (MediaRoute2Info route : mRouterManager.getTransferableRoutes(mPackageName)) {
+ for (MediaRoute2Info route : getAvailableRoutes(mPackageName)) {
if (DEBUG) {
Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
+ route.getVolume() + ", type : " + route.getType());
@@ -465,6 +455,29 @@
}
}
+ private List<MediaRoute2Info> getAvailableRoutes(String packageName) {
+ final List<MediaRoute2Info> infos = new ArrayList<>();
+ RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName);
+ if (routingSessionInfo != null) {
+ infos.addAll(mRouterManager.getSelectedRoutes(routingSessionInfo));
+ }
+ final List<MediaRoute2Info> transferableRoutes =
+ mRouterManager.getTransferableRoutes(packageName);
+ for (MediaRoute2Info transferableRoute : transferableRoutes) {
+ boolean alreadyAdded = false;
+ for (MediaRoute2Info mediaRoute2Info : infos) {
+ if (TextUtils.equals(transferableRoute.getId(), mediaRoute2Info.getId())) {
+ alreadyAdded = true;
+ break;
+ }
+ }
+ if (!alreadyAdded) {
+ infos.add(transferableRoute);
+ }
+ }
+ return infos;
+ }
+
@VisibleForTesting
void addMediaDevice(MediaRoute2Info route) {
final int deviceType = route.getType();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index a8da2c0..22001c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -22,11 +22,13 @@
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.RoutingSessionInfo;
+import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.A2dpProfile;
@@ -49,6 +51,7 @@
/**
* LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
*/
+@RequiresApi(Build.VERSION_CODES.R)
public class LocalMediaManager implements BluetoothCallback {
private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
private static final String TAG = "LocalMediaManager";
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
index d153bd8..ab17499 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
@@ -56,4 +56,16 @@
default double getValue(int id, double def) {
return def;
}
+
+ /** Add a listener to be alerted when any flag changes. */
+ default void addListener(Listener listener) {}
+
+ /** Remove a listener to be alerted when any flag changes. */
+ default void removeListener(Listener listener) {}
+
+ /** A simple listener to be alerted when a flag changes. */
+ interface Listener {
+ /** */
+ void onFlagChanged(int id);
+ }
}
diff --git a/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml
new file mode 100644
index 0000000..181ba07
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2021, 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.
+-->
+<com.android.systemui.qs.QSFooterActionsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/qs_footer_actions_container"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:gravity="center_vertical">
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@android:id/edit"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:contentDescription="@string/accessibility_quick_settings_edit"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_mode_edit"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_expanded_size"
+ android:layout_height="@dimen/multi_user_avatar_expanded_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside" />
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/pm_lite"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_lock_power_off"
+ android:contentDescription="@string/accessibility_quick_settings_power_menu"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/settings_button_container"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <com.android.systemui.statusbar.phone.SettingsButton
+ android:id="@+id/settings_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_gravity="center"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
+ android:background="@drawable/qs_footer_action_chip_background_borderless"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_settings"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/tuner_icon"
+ android:layout_width="8dp"
+ android:layout_height="8dp"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginBottom="@dimen/qs_footer_icon_padding"
+ android:src="@drawable/tuner"
+ android:tint="?android:attr/textColorTertiary"
+ android:visibility="invisible" />
+
+ </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+</com.android.systemui.qs.QSFooterActionsView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/fp_to_unlock.xml b/packages/SystemUI/res/anim/fp_to_unlock.xml
index a348208..a5f75b6 100644
--- a/packages/SystemUI/res/anim/fp_to_unlock.xml
+++ b/packages/SystemUI/res/anim/fp_to_unlock.xml
@@ -19,10 +19,10 @@
<group android:name="_R_G">
<group android:name="_R_G_L_1_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
<group android:name="_R_G_L_1_G" android:translateX="30.75" android:translateY="25.75">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
- <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
- <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
- <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
+ <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
+ <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
+ <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
</group>
</group>
<group android:name="_R_G_L_0_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
diff --git a/packages/SystemUI/res/anim/lock_to_unlock.xml b/packages/SystemUI/res/anim/lock_to_unlock.xml
index ec51c01..76f7a05 100644
--- a/packages/SystemUI/res/anim/lock_to_unlock.xml
+++ b/packages/SystemUI/res/anim/lock_to_unlock.xml
@@ -21,7 +21,7 @@
<group android:name="_R_G_L_2_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_2_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_2_G" android:translateX="-0.375" android:translateY="-22.375">
- <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
+ <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
</group>
</group>
</group>
@@ -30,7 +30,7 @@
<group android:name="_R_G_L_1_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_1_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_1_G" android:translateX="5" android:translateY="-22.5">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
</group>
</group>
</group>
diff --git a/packages/SystemUI/res/drawable/ic_unlock.xml b/packages/SystemUI/res/drawable/ic_unlock.xml
index c3b3469..46023e6 100644
--- a/packages/SystemUI/res/drawable/ic_unlock.xml
+++ b/packages/SystemUI/res/drawable/ic_unlock.xml
@@ -21,7 +21,7 @@
android:strokeColor="#FF000000"
android:strokeLineCap="round"
android:strokeLineJoin="round"
- android:strokeWidth="2.5"
+ android:strokeWidth="2"
android:pathData="M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
</group>
<group android:translateX="14" android:translateY="13.5">
@@ -29,7 +29,7 @@
android:strokeColor="#FF000000"
android:strokeLineCap="round"
android:strokeLineJoin="round"
- android:strokeWidth="2.5"
+ android:strokeWidth="2"
android:pathData="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " />
</group>
<group android:translateX="20" android:translateY="35.75">
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 317dbc0..fe0b14a 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -68,93 +68,8 @@
</LinearLayout>
- <LinearLayout
- android:id="@+id/qs_footer_actions_container"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:gravity="center_vertical">
+ <include layout="@layout/qs_footer_actions"/>
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@android:id/edit"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:contentDescription="@string/accessibility_quick_settings_edit"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_mode_edit"
- android:tint="?android:attr/textColorPrimary" />
-
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_expanded_size"
- android:layout_height="@dimen/multi_user_avatar_expanded_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?android:attr/textColorPrimary" />
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_chip_background"
- android:layout_weight="1"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_gravity="center"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:background="@drawable/qs_footer_action_chip_background_borderless"
- android:padding="@dimen/qs_footer_icon_padding"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/textColorPrimary" />
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/tuner_icon"
- android:layout_width="8dp"
- android:layout_height="8dp"
- android:layout_gravity="center_horizontal|bottom"
- android:layout_marginBottom="@dimen/qs_footer_icon_padding"
- android:src="@drawable/tuner"
- android:tint="?android:attr/textColorTertiary"
- android:visibility="invisible" />
-
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
- </LinearLayout>
</LinearLayout>
</com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index e37a3f0..0c3c602 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -68,7 +68,7 @@
android:id="@+id/lock_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="48px"
+ android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"/>
</com.android.keyguard.LockIconView>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view.xml b/packages/SystemUI/res/layout/udfps_keyguard_view.xml
index 18517906..a9eb27a 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view.xml
@@ -34,7 +34,7 @@
android:id="@+id/udfps_aod_fp"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="48px"
+ android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"
app:lottie_autoPlay="false"
@@ -46,7 +46,7 @@
android:id="@+id/udfps_lockscreen_fp"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="48px"
+ android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"
app:lottie_autoPlay="false"
diff --git a/packages/SystemUI/res/raw/udfps_aod_fp.json b/packages/SystemUI/res/raw/udfps_aod_fp.json
index 3247fe7..3b273ff 100644
--- a/packages/SystemUI/res/raw/udfps_aod_fp.json
+++ b/packages/SystemUI/res/raw/udfps_aod_fp.json
@@ -164,7 +164,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
@@ -526,7 +526,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
@@ -829,7 +829,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
@@ -1132,7 +1132,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
@@ -1507,7 +1507,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
@@ -1882,7 +1882,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
@@ -2257,7 +2257,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
@@ -2560,7 +2560,7 @@
},
"w":{
"a":0,
- "k":1,
+ "k":1.3,
"ix":5
},
"lc":2,
diff --git a/packages/SystemUI/res/raw/udfps_lockscreen_fp.json b/packages/SystemUI/res/raw/udfps_lockscreen_fp.json
index a25a475..a30a03a 100644
--- a/packages/SystemUI/res/raw/udfps_lockscreen_fp.json
+++ b/packages/SystemUI/res/raw/udfps_lockscreen_fp.json
@@ -1 +1 @@
-{"v":"5.7.8","fr":60,"ip":0,"op":46,"w":46,"h":65,"nm":"fingerprint_build_on","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Fingerprint_20210701 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[23.091,32.5,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999994755,0.949000000954,0.624000012875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":210,"cm":"2","dr":0},{"tm":255,"cm":"1","dr":0}]}
\ No newline at end of file
+{"v":"5.7.8","fr":60,"ip":0,"op":46,"w":46,"h":65,"nm":"fingerprint_build_on","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Fingerprint_20210701 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[23.091,32.5,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999994755,0.949000000954,0.624000012875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":210,"cm":"2","dr":0},{"tm":255,"cm":"1","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a12fd2b..28233f8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -538,6 +538,9 @@
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
+ <!-- Padding for the lock icon on the keyguard. In pixels - should not scale with display size. -->
+ <dimen name="lock_icon_padding">48px</dimen>
+
<!-- Height of the carrier/wifi name label -->
<dimen name="carrier_label_height">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 35001af..b9002c3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3014,6 +3014,8 @@
<string name="pref_title_network_details" msgid="7329759534269363308">"Network details"</string>
<!-- Provider Model: Panel subtitle for tapping a network to connect to internet. [CHAR LIMIT=60] -->
<string name="tap_a_network_to_connect">Tap a network to connect</string>
+ <!-- Provider Model: Panel subtitle for unlocking screen to view networks. [CHAR LIMIT=60] -->
+ <string name="unlock_to_view_networks">Unlock to view networks</string>
<!-- Provider Model: Wi-Fi settings. text displayed when Wi-Fi is on and network list is empty [CHAR LIMIT=50]-->
<string name="wifi_empty_list_wifi_on">Searching for networks\u2026</string>
<!-- Provider Model: Failure notification for connect -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 16f313f..6908409 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -347,13 +347,16 @@
private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
private static final int HAL_ERROR_RETRY_MAX = 20;
- private final Runnable mCancelNotReceived = new Runnable() {
- @Override
- public void run() {
- Log.w(TAG, "Cancel not received, transitioning to STOPPED");
- mFingerprintRunningState = mFaceRunningState = BIOMETRIC_STATE_STOPPED;
- updateBiometricListeningState();
- }
+ private final Runnable mFpCancelNotReceived = () -> {
+ Log.e(TAG, "Fp cancellation not received, transitioning to STOPPED");
+ mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ updateFingerprintListeningState();
+ };
+
+ private final Runnable mFaceCancelNotReceived = () -> {
+ Log.e(TAG, "Face cancellation not received, transitioning to STOPPED");
+ mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ updateFaceListeningState();
};
private final Handler mHandler;
@@ -791,19 +794,19 @@
private void handleFingerprintError(int msgId, String errString) {
Assert.isMainThread();
- if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED && mHandler.hasCallbacks(
- mCancelNotReceived)) {
- mHandler.removeCallbacks(mCancelNotReceived);
+ if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
+ mHandler.removeCallbacks(mFpCancelNotReceived);
}
+ // Error is always the end of authentication lifecycle.
+ mFingerprintCancelSignal = null;
+
if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
&& mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
updateFingerprintListeningState();
} else {
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
- mFingerprintCancelSignal = null;
- mFaceCancelSignal = null;
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
@@ -905,6 +908,7 @@
private void handleFaceAuthFailed() {
Assert.isMainThread();
+ mFaceCancelSignal = null;
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -983,10 +987,13 @@
private void handleFaceError(int msgId, String errString) {
Assert.isMainThread();
if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString);
- if (msgId == FaceManager.FACE_ERROR_CANCELED && mHandler.hasCallbacks(mCancelNotReceived)) {
- mHandler.removeCallbacks(mCancelNotReceived);
+ if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
+ mHandler.removeCallbacks(mFaceCancelNotReceived);
}
+ // Error is always the end of authentication lifecycle
+ mFaceCancelSignal = null;
+
if (msgId == FaceManager.FACE_ERROR_CANCELED
&& mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -2368,6 +2375,14 @@
}
private void startListeningForFingerprint() {
+ final int userId = getCurrentUser();
+ final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
+ if (mFingerprintCancelSignal != null) {
+ Log.e(TAG, "Cancellation signal is not null, high chance of bug in fp auth lifecycle"
+ + " management. FP state: " + mFingerprintRunningState
+ + ", unlockPossible: " + unlockPossible);
+ }
+
if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
return;
@@ -2377,11 +2392,8 @@
return;
}
if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
- int userId = getCurrentUser();
- if (isUnlockWithFingerprintPossible(userId)) {
- if (mFingerprintCancelSignal != null) {
- mFingerprintCancelSignal.cancel();
- }
+
+ if (unlockPossible) {
mFingerprintCancelSignal = new CancellationSignal();
if (isEncryptedOrLockdown(userId)) {
@@ -2397,6 +2409,14 @@
}
private void startListeningForFace() {
+ final int userId = getCurrentUser();
+ final boolean unlockPossible = isUnlockWithFacePossible(userId);
+ if (mFaceCancelSignal != null) {
+ Log.e(TAG, "Cancellation signal is not null, high chance of bug in face auth lifecycle"
+ + " management. Face state: " + mFaceRunningState
+ + ", unlockPossible: " + unlockPossible);
+ }
+
if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
return;
@@ -2405,11 +2425,8 @@
return;
}
if (DEBUG) Log.v(TAG, "startListeningForFace(): " + mFaceRunningState);
- int userId = getCurrentUser();
- if (isUnlockWithFacePossible(userId)) {
- if (mFaceCancelSignal != null) {
- mFaceCancelSignal.cancel();
- }
+
+ if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
// This would need to be updated for multi-sensor devices
@@ -2461,9 +2478,8 @@
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
mFingerprintCancelSignal = null;
- if (!mHandler.hasCallbacks(mCancelNotReceived)) {
- mHandler.postDelayed(mCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
- }
+ mHandler.removeCallbacks(mFpCancelNotReceived);
+ mHandler.postDelayed(mFpCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
}
setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING);
}
@@ -2478,9 +2494,8 @@
if (mFaceCancelSignal != null) {
mFaceCancelSignal.cancel();
mFaceCancelSignal = null;
- if (!mHandler.hasCallbacks(mCancelNotReceived)) {
- mHandler.postDelayed(mCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
- }
+ mHandler.removeCallbacks(mFaceCancelNotReceived);
+ mHandler.postDelayed(mFaceCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
}
setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index 08247a8..d4d01c8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -114,6 +114,14 @@
return mPlugin.getValue(flag.getId(), flag.getDefault());
}
+ void addListener(FlagReaderPlugin.Listener listener) {
+ mPlugin.addListener(listener);
+ }
+
+ void removeListener(FlagReaderPlugin.Listener listener) {
+ mPlugin.removeListener(listener);
+ }
+
/**
* Returns true if the specified feature flag has been enabled.
*
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 0c9e6de..e51f90f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -19,11 +19,14 @@
import android.content.Context;
import android.util.FeatureFlagUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.FlagReaderPlugin;
-import java.lang.reflect.Field;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.inject.Inject;
@@ -37,11 +40,26 @@
public class FeatureFlags {
private final FeatureFlagReader mFlagReader;
private final Context mContext;
+ private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>();
+ private final Map<Integer, List<Listener>> mListeners = new HashMap<>();
@Inject
public FeatureFlags(FeatureFlagReader flagReader, Context context) {
mFlagReader = flagReader;
mContext = context;
+
+ flagReader.addListener(mListener);
+ }
+
+ private final FlagReaderPlugin.Listener mListener = id -> {
+ if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) {
+ mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id)));
+ }
+ };
+
+ @VisibleForTesting
+ void addFlag(Flag flag) {
+ mFlagMap.put(flag.getId(), flag);
}
/**
@@ -92,6 +110,20 @@
return mFlagReader.getValue(flag);
}
+ /** Add a listener for a specific flag. */
+ public void addFlagListener(Flag<?> flag, Listener listener) {
+ mListeners.putIfAbsent(flag.getId(), new ArrayList<>());
+ mListeners.get(flag.getId()).add(listener);
+ mFlagMap.putIfAbsent(flag.getId(), flag);
+ }
+
+ /** Remove a listener for a specific flag. */
+ public void removeFlagListener(Flag<?> flag, Listener listener) {
+ if (mListeners.containsKey(flag.getId())) {
+ mListeners.get(flag.getId()).remove(listener);
+ }
+ }
+
public boolean isNewNotifPipelineEnabled() {
return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
}
@@ -160,27 +192,6 @@
return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
}
- private Map<Integer, Flag<?>> collectFlags() {
- Map<Integer, Flag<?>> flags = new HashMap<>();
-
- Field[] fields = this.getClass().getFields();
-
- for (Field field : fields) {
- Class<?> t = field.getType();
- if (Flag.class.isAssignableFrom(t)) {
- try {
- //flags.add((Flag<?>) field.get(null));
- Flag flag = (Flag) field.get(null);
- flags.put(flag.getId(), flag);
- } catch (IllegalAccessException e) {
- // no-op
- }
- }
- }
-
- return flags;
- }
-
/** Simple interface for beinga alerted when a specific flag changes value. */
public interface Listener {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 85f2366..2817718 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2603,7 +2603,7 @@
if (mContext.getResources().getBoolean(
com.android.internal.R.bool.config_guestUserAutoCreated)) {
// TODO(b/191067027): Move post-boot guest creation to system_server
- mUserSwitcherController.guaranteeGuestPresent();
+ mUserSwitcherController.schedulePostBootGuestCreation();
}
mBootCompleted = true;
adjustStatusBarLocked(false, true);
@@ -2759,7 +2759,10 @@
// Don't hide the keyguard due to a doze change if there's a lock pending, because we're
// just going to show it again.
- if (mShowing || !mPendingLock) {
+ // If the device is not capable of controlling the screen off animation, SysUI needs to
+ // update lock screen state in ATMS here, otherwise ATMS tries to resume activities when
+ // enabling doze state.
+ if (mShowing || !mPendingLock || !mDozeParameters.canControlUnlockedScreenOff()) {
setShowingLocked(mShowing);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 543004e..1ca2217 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -116,7 +116,7 @@
private final TaskbarDelegate mTaskbarDelegate;
private final NotificationShadeDepthController mNotificationShadeDepthController;
private int mNavMode;
- private boolean mIsTablet;
+ @VisibleForTesting boolean mIsTablet;
private final UserTracker mUserTracker;
/** A displayId - nav bar maps. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index e38bd4b..0e0681b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -35,11 +35,6 @@
void setExpanded(boolean expanded);
/**
- * Returns the full height of the footer.
- */
- int getHeight();
-
- /**
* Sets the percentage amount that the quick settings has been expanded.
*
* @param expansion A value from 1 to 0 that indicates how much the quick settings have been
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt
new file mode 100644
index 0000000..dbf62a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt
@@ -0,0 +1,167 @@
+package com.android.systemui.qs
+
+import android.content.Intent
+import android.os.UserManager
+import android.provider.Settings
+import android.view.View
+import android.widget.Toast
+import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.nano.MetricsProto
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.phone.SettingsButton
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.ViewController
+import javax.inject.Inject
+import javax.inject.Named
+
+class QSFooterActionsController @Inject constructor(
+ view: QSFooterActionsView,
+ private val qsPanelController: QSPanelController,
+ private val activityStarter: ActivityStarter,
+ private val userManager: UserManager,
+ private val userInfoController: UserInfoController,
+ private val multiUserSwitchController: MultiUserSwitchController,
+ private val deviceProvisionedController: DeviceProvisionedController,
+ private val falsingManager: FalsingManager,
+ private val metricsLogger: MetricsLogger,
+ private val tunerService: TunerService,
+ private val globalActionsDialog: GlobalActionsDialogLite,
+ private val uiEventLogger: UiEventLogger,
+ @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean
+) : ViewController<QSFooterActionsView>(view) {
+
+ private var listening: Boolean = false
+ var expanded = false
+ set(value) {
+ field = value
+ mView.setExpanded(value, isTunerEnabled(),
+ multiUserSwitchController.isMultiUserEnabled)
+ }
+
+ private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
+ private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
+ private val editButton: View = view.findViewById(android.R.id.edit)
+ private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
+
+ private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
+ val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
+ mView.onUserInfoChanged(picture, isGuestUser)
+ }
+
+ private val onClickListener = View.OnClickListener { v ->
+ // Don't do anything until views are unhidden. Don't do anything if the tap looks
+ // suspicious.
+ if (!expanded || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return@OnClickListener
+ }
+ if (v === settingsButton) {
+ if (!deviceProvisionedController.isCurrentUserSetup) {
+ // If user isn't setup just unlock the device and dump them back at SUW.
+ activityStarter.postQSRunnableDismissingKeyguard {}
+ return@OnClickListener
+ }
+ metricsLogger.action(
+ if (expanded) MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
+ else MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH)
+ if (settingsButton.isTunerClick) {
+ activityStarter.postQSRunnableDismissingKeyguard {
+ if (isTunerEnabled()) {
+ tunerService.showResetRequest {
+ // Relaunch settings so that the tuner disappears.
+ startSettingsActivity()
+ }
+ } else {
+ Toast.makeText(context, R.string.tuner_toast, Toast.LENGTH_LONG).show()
+ tunerService.isTunerEnabled = true
+ }
+ startSettingsActivity()
+ }
+ } else {
+ startSettingsActivity()
+ }
+ } else if (v === powerMenuLite) {
+ uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+ globalActionsDialog.showOrHideDialog(false, true)
+ }
+ }
+
+ override fun onInit() {
+ multiUserSwitchController.init()
+ }
+
+ private fun startSettingsActivity() {
+ val animationController = settingsButtonContainer?.let {
+ ActivityLaunchAnimator.Controller.fromView(
+ it,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON)
+ }
+ activityStarter.startActivity(Intent(Settings.ACTION_SETTINGS),
+ true /* dismissShade */, animationController)
+ }
+
+ @VisibleForTesting
+ public override fun onViewAttached() {
+ if (showPMLiteButton) {
+ powerMenuLite.visibility = View.VISIBLE
+ powerMenuLite.setOnClickListener(onClickListener)
+ } else {
+ powerMenuLite.visibility = View.GONE
+ }
+ settingsButton.setOnClickListener(onClickListener)
+ editButton.setOnClickListener(View.OnClickListener { view: View? ->
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return@OnClickListener
+ }
+ activityStarter.postQSRunnableDismissingKeyguard { qsPanelController.showEdit(view) }
+ })
+
+ mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
+ }
+
+ override fun onViewDetached() {
+ setListening(false)
+ }
+
+ fun setListening(listening: Boolean) {
+ if (this.listening == listening) {
+ return
+ }
+ this.listening = listening
+ if (this.listening) {
+ userInfoController.addCallback(onUserInfoChangedListener)
+ } else {
+ userInfoController.removeCallback(onUserInfoChangedListener)
+ }
+ }
+
+ fun disable(state2: Int) {
+ mView.disable(state2, isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
+ }
+
+ fun setExpansion(headerExpansionFraction: Float) {
+ mView.setExpansion(headerExpansionFraction)
+ }
+
+ fun updateAnimator(width: Int, numTiles: Int) {
+ mView.updateAnimator(width, numTiles)
+ }
+
+ fun setKeyguardShowing() {
+ mView.setKeyguardShowing()
+ }
+
+ private fun isTunerEnabled() = tunerService.isTunerEnabled
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt
new file mode 100644
index 0000000..66a29a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.PorterDuff
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.RippleDrawable
+import android.os.UserManager
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.settingslib.Utils
+import com.android.settingslib.drawable.UserIconDrawable
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.MultiUserSwitch
+import com.android.systemui.statusbar.phone.SettingsButton
+
+/**
+ * Quick Settings bottom buttons placed in footer (aka utility bar) - always visible in expanded QS,
+ * in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings,
+ * edit tiles, power off and conditionally: user switch and tuner
+ */
+class QSFooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
+ private lateinit var settingsContainer: View
+ private lateinit var settingsButton: SettingsButton
+ private lateinit var multiUserSwitch: MultiUserSwitch
+ private lateinit var multiUserAvatar: ImageView
+ private lateinit var tunerIcon: View
+ private lateinit var editTilesButton: View
+
+ private var settingsCogAnimator: TouchAnimator? = null
+
+ private var qsDisabled = false
+ private var isExpanded = false
+ private var expansionAmount = 0f
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ editTilesButton = requireViewById(android.R.id.edit)
+ settingsButton = findViewById(R.id.settings_button)
+ settingsContainer = findViewById(R.id.settings_button_container)
+ multiUserSwitch = findViewById(R.id.multi_user_switch)
+ multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar)
+ tunerIcon = requireViewById(R.id.tuner_icon)
+
+ // RenderThread is doing more harm than good when touching the header (to expand quick
+ // settings), so disable it for this view
+ if (settingsButton.background is RippleDrawable) {
+ (settingsButton.background as RippleDrawable).setForceSoftware(true)
+ }
+ updateResources()
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+
+ fun updateAnimator(width: Int, numTiles: Int) {
+ val size = (mContext.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) -
+ mContext.resources.getDimensionPixelSize(R.dimen.qs_tile_padding))
+ val remaining = (width - numTiles * size) / (numTiles - 1)
+ val defSpace = mContext.resources.getDimensionPixelOffset(R.dimen.default_gear_space)
+ val translation = if (isLayoutRtl) (remaining - defSpace) else -(remaining - defSpace)
+ settingsCogAnimator = TouchAnimator.Builder()
+ .addFloat(settingsButton, "translationX", translation.toFloat(), 0f)
+ .addFloat(settingsButton, "rotation", -120f, 0f)
+ .build()
+ setExpansion(expansionAmount)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ updateResources()
+ }
+
+ override fun onRtlPropertiesChanged(layoutDirection: Int) {
+ super.onRtlPropertiesChanged(layoutDirection)
+ updateResources()
+ }
+
+ private fun updateResources() {
+ val tunerIconTranslation = mContext.resources
+ .getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation).toFloat()
+ tunerIcon.translationX = if (isLayoutRtl) (-tunerIconTranslation) else tunerIconTranslation
+ }
+
+ fun setKeyguardShowing() {
+ setExpansion(expansionAmount)
+ }
+
+ fun setExpanded(expanded: Boolean, isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+ if (isExpanded == expanded) return
+ isExpanded = expanded
+ updateEverything(isTunerEnabled, multiUserEnabled)
+ }
+
+ fun setExpansion(headerExpansionFraction: Float) {
+ expansionAmount = headerExpansionFraction
+ if (settingsCogAnimator != null) settingsCogAnimator!!.setPosition(headerExpansionFraction)
+ }
+
+ fun disable(state2: Int, isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+ val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+ if (disabled == qsDisabled) return
+ qsDisabled = disabled
+ updateEverything(isTunerEnabled, multiUserEnabled)
+ }
+
+ fun updateEverything(isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+ post {
+ updateVisibilities(isTunerEnabled, multiUserEnabled)
+ updateClickabilities()
+ isClickable = false
+ }
+ }
+
+ private fun updateClickabilities() {
+ multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
+ editTilesButton.isClickable = editTilesButton.visibility == VISIBLE
+ settingsButton.isClickable = settingsButton.visibility == VISIBLE
+ }
+
+ private fun updateVisibilities(isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+ settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
+ tunerIcon.visibility = if (isTunerEnabled) VISIBLE else INVISIBLE
+ multiUserSwitch.visibility = if (showUserSwitcher(multiUserEnabled)) VISIBLE else GONE
+ val isDemo = UserManager.isDeviceInDemoMode(context)
+ settingsButton.visibility = if (isDemo || !isExpanded) INVISIBLE else VISIBLE
+ }
+
+ private fun showUserSwitcher(multiUserEnabled: Boolean): Boolean {
+ return isExpanded && multiUserEnabled
+ }
+
+ fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) {
+ var pictureToSet = picture
+ if (picture != null && isGuestUser && picture !is UserIconDrawable) {
+ pictureToSet = picture.constantState.newDrawable(resources).mutate()
+ pictureToSet.setColorFilter(
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
+ PorterDuff.Mode.SRC_IN)
+ }
+ multiUserAvatar.setImageDrawable(pictureToSet)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 57438d1..7db13bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -21,60 +21,40 @@
import android.content.Context;
import android.content.res.Configuration;
import android.database.ContentObserver;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.settingslib.Utils;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
-import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.R;
-import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.statusbar.phone.MultiUserSwitch;
-import com.android.systemui.statusbar.phone.SettingsButton;
-/** */
+/**
+ * Footer of expanded Quick Settings, tiles page indicator, (optionally) build number and
+ * {@link QSFooterActionsView}
+ */
public class QSFooterView extends FrameLayout {
- private SettingsButton mSettingsButton;
- protected View mSettingsContainer;
private PageIndicator mPageIndicator;
private TextView mBuildText;
- private boolean mShouldShowBuildText;
-
- private boolean mQsDisabled;
-
- private boolean mExpanded;
-
- private boolean mListening;
-
- protected MultiUserSwitch mMultiUserSwitch;
- private ImageView mMultiUserAvatar;
+ private View mActionsContainer;
protected TouchAnimator mFooterAnimator;
+
+ private boolean mQsDisabled;
+ private boolean mExpanded;
private float mExpansionAmount;
- protected View mEdit;
- private TouchAnimator mSettingsCogAnimator;
-
- private View mActionsContainer;
- private View mTunerIcon;
- private int mTunerIconTranslation;
+ private boolean mShouldShowBuildText;
private OnClickListener mExpandClickListener;
@@ -94,27 +74,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mEdit = requireViewById(android.R.id.edit);
-
mPageIndicator = findViewById(R.id.footer_page_indicator);
-
- mSettingsButton = findViewById(R.id.settings_button);
- mSettingsContainer = findViewById(R.id.settings_button_container);
-
- mMultiUserSwitch = findViewById(R.id.multi_user_switch);
- mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
-
mActionsContainer = requireViewById(R.id.qs_footer_actions_container);
mBuildText = findViewById(R.id.build);
- mTunerIcon = requireViewById(R.id.tuner_icon);
- // RenderThread is doing more harm than good when touching the header (to expand quick
- // settings), so disable it for this view
- if (mSettingsButton.getBackground() instanceof RippleDrawable) {
- ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
- }
updateResources();
-
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
setBuildText();
}
@@ -137,18 +101,7 @@
}
}
- void updateAnimator(int width, int numTiles) {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
- - mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_padding);
- int remaining = (width - numTiles * size) / (numTiles - 1);
- int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
-
- mSettingsCogAnimator = new Builder()
- .addFloat(mSettingsButton, "translationX",
- isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
- .addFloat(mSettingsButton, "rotation", -120, 0)
- .build();
-
+ void updateExpansion() {
setExpansion(mExpansionAmount);
}
@@ -158,20 +111,11 @@
updateResources();
}
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
- updateResources();
- }
-
private void updateResources() {
updateFooterAnimator();
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
setLayoutParams(lp);
- mTunerIconTranslation = mContext.getResources()
- .getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation);
- mTunerIcon.setTranslationX(isLayoutRtl() ? -mTunerIconTranslation : mTunerIconTranslation);
}
private void updateFooterAnimator() {
@@ -197,17 +141,15 @@
mExpandClickListener = onClickListener;
}
- void setExpanded(boolean expanded, boolean isTunerEnabled, boolean multiUserEnabled) {
+ void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- updateEverything(isTunerEnabled, multiUserEnabled);
+ updateEverything();
}
/** */
public void setExpansion(float headerExpansionFraction) {
mExpansionAmount = headerExpansionFraction;
- if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
-
if (mFooterAnimator != null) {
mFooterAnimator.setPosition(headerExpansionFraction);
}
@@ -228,14 +170,6 @@
super.onDetachedFromWindow();
}
- /** */
- public void setListening(boolean listening) {
- if (listening == mListening) {
- return;
- }
- mListening = listening;
- }
-
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
@@ -253,50 +187,26 @@
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
}
- void disable(int state2, boolean isTunerEnabled, boolean multiUserEnabled) {
+ void disable(int state2) {
final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
- updateEverything(isTunerEnabled, multiUserEnabled);
+ updateEverything();
}
- void updateEverything(boolean isTunerEnabled, boolean multiUserEnabled) {
+ void updateEverything() {
post(() -> {
- updateVisibilities(isTunerEnabled, multiUserEnabled);
+ updateVisibilities();
updateClickabilities();
setClickable(false);
});
}
private void updateClickabilities() {
- mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
- mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
- mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
- private void updateVisibilities(boolean isTunerEnabled, boolean multiUserEnabled) {
- mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
- mTunerIcon.setVisibility(isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
- final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
- mMultiUserSwitch.setVisibility(
- showUserSwitcher(multiUserEnabled) ? View.VISIBLE : View.GONE);
- mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
-
+ private void updateVisibilities() {
mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.INVISIBLE);
}
-
- private boolean showUserSwitcher(boolean multiUserEnabled) {
- return mExpanded && multiUserEnabled;
- }
-
- void onUserInfoChanged(Drawable picture, boolean isGuestUser) {
- if (picture != null && isGuestUser && !(picture instanceof UserIconDrawable)) {
- picture = picture.getConstantState().newDrawable(getResources()).mutate();
- picture.setColorFilter(
- Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
- Mode.SRC_IN);
- }
- mMultiUserAvatar.setImageDrawable(picture);
- }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 929aeda..c8ae590 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -16,39 +16,19 @@
package com.android.systemui.qs;
-import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED;
-
import android.content.ClipData;
import android.content.ClipboardManager;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.globalactions.GlobalActionsDialogLite;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.MultiUserSwitchController;
-import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
-import javax.inject.Named;
/**
* Controller for {@link QSFooterView}.
@@ -56,137 +36,45 @@
@QSScope
public class QSFooterViewController extends ViewController<QSFooterView> implements QSFooter {
- private final UserManager mUserManager;
- private final UserInfoController mUserInfoController;
- private final ActivityStarter mActivityStarter;
- private final DeviceProvisionedController mDeviceProvisionedController;
private final UserTracker mUserTracker;
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
- private final TunerService mTunerService;
- private final MetricsLogger mMetricsLogger;
- private final FalsingManager mFalsingManager;
- private final MultiUserSwitchController mMultiUserSwitchController;
- private final SettingsButton mSettingsButton;
- private final View mSettingsButtonContainer;
+ private final QSFooterActionsController mQsFooterActionsController;
private final TextView mBuildText;
- private final View mEdit;
private final PageIndicator mPageIndicator;
- private final View mPowerMenuLite;
- private final boolean mShowPMLiteButton;
- private final GlobalActionsDialogLite mGlobalActionsDialog;
- private final UiEventLogger mUiEventLogger;
-
- private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
- new UserInfoController.OnUserInfoChangedListener() {
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- boolean isGuestUser = mUserManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser());
- mView.onUserInfoChanged(picture, isGuestUser);
- }
- };
-
- private final View.OnClickListener mSettingsOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Don't do anything until views are unhidden. Don't do anything if the tap looks
- // suspicious.
- if (!mExpanded || mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
-
- if (v == mSettingsButton) {
- if (!mDeviceProvisionedController.isCurrentUserSetup()) {
- // If user isn't setup just unlock the device and dump them back at SUW.
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- });
- return;
- }
- mMetricsLogger.action(
- mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
- : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
- if (mSettingsButton.isTunerClick()) {
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- if (isTunerEnabled()) {
- mTunerService.showResetRequest(
- () -> {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- });
- } else {
- Toast.makeText(getContext(), R.string.tuner_toast,
- Toast.LENGTH_LONG).show();
- mTunerService.setTunerEnabled(true);
- }
- startSettingsActivity();
-
- });
- } else {
- startSettingsActivity();
- }
- } else if (v == mPowerMenuLite) {
- mUiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
- mGlobalActionsDialog.showOrHideDialog(false, true);
- }
- }
- };
-
- private boolean mListening;
- private boolean mExpanded;
@Inject
- QSFooterViewController(QSFooterView view, UserManager userManager,
- UserInfoController userInfoController, ActivityStarter activityStarter,
- DeviceProvisionedController deviceProvisionedController, UserTracker userTracker,
+ QSFooterViewController(QSFooterView view,
+ UserTracker userTracker,
QSPanelController qsPanelController,
- MultiUserSwitchController multiUserSwitchController,
QuickQSPanelController quickQSPanelController,
- TunerService tunerService, MetricsLogger metricsLogger, FalsingManager falsingManager,
- @Named(PM_LITE_ENABLED) boolean showPMLiteButton,
- GlobalActionsDialogLite globalActionsDialog, UiEventLogger uiEventLogger) {
+ QSFooterActionsController qsFooterActionsController) {
super(view);
- mUserManager = userManager;
- mUserInfoController = userInfoController;
- mActivityStarter = activityStarter;
- mDeviceProvisionedController = deviceProvisionedController;
mUserTracker = userTracker;
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
- mTunerService = tunerService;
- mMetricsLogger = metricsLogger;
- mFalsingManager = falsingManager;
- mMultiUserSwitchController = multiUserSwitchController;
+ mQsFooterActionsController = qsFooterActionsController;
- mSettingsButton = mView.findViewById(R.id.settings_button);
- mSettingsButtonContainer = mView.findViewById(R.id.settings_button_container);
mBuildText = mView.findViewById(R.id.build);
- mEdit = mView.findViewById(android.R.id.edit);
mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
- mPowerMenuLite = mView.findViewById(R.id.pm_lite);
- mShowPMLiteButton = showPMLiteButton;
- mGlobalActionsDialog = globalActionsDialog;
- mUiEventLogger = uiEventLogger;
}
@Override
protected void onInit() {
super.onInit();
- mMultiUserSwitchController.init();
+ mQsFooterActionsController.init();
}
@Override
protected void onViewAttached() {
- if (mShowPMLiteButton) {
- mPowerMenuLite.setVisibility(View.VISIBLE);
- mPowerMenuLite.setOnClickListener(mSettingsOnClickListener);
- } else {
- mPowerMenuLite.setVisibility(View.GONE);
- }
mView.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
- mView.updateAnimator(
- right - left, mQuickQSPanelController.getNumQuickTiles()));
- mSettingsButton.setOnClickListener(mSettingsOnClickListener);
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mView.updateExpansion();
+ mQsFooterActionsController.updateAnimator(right - left,
+ mQuickQSPanelController.getNumQuickTiles());
+ }
+ );
+
mBuildText.setOnLongClickListener(view -> {
CharSequence buildText = mBuildText.getText();
if (!TextUtils.isEmpty(buildText)) {
@@ -200,17 +88,8 @@
}
return false;
});
-
- mEdit.setOnClickListener(view -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
- mActivityStarter.postQSRunnableDismissingKeyguard(() ->
- mQsPanelController.showEdit(view));
- });
-
mQsPanelController.setFooterPageIndicator(mPageIndicator);
- mView.updateEverything(isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
+ mView.updateEverything();
}
@Override
@@ -225,38 +104,25 @@
@Override
public void setExpanded(boolean expanded) {
- mExpanded = expanded;
- mView.setExpanded(
- expanded, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
- }
-
- @Override
- public int getHeight() {
- return mView.getHeight();
+ mQsFooterActionsController.setExpanded(expanded);
+ mView.setExpanded(expanded);
}
@Override
public void setExpansion(float expansion) {
mView.setExpansion(expansion);
+ mQsFooterActionsController.setExpansion(expansion);
}
@Override
public void setListening(boolean listening) {
- if (mListening == listening) {
- return;
- }
-
- mListening = listening;
- if (mListening) {
- mUserInfoController.addCallback(mOnUserInfoChangedListener);
- } else {
- mUserInfoController.removeCallback(mOnUserInfoChangedListener);
- }
+ mQsFooterActionsController.setListening(listening);
}
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
mView.setKeyguardShowing();
+ mQsFooterActionsController.setKeyguardShowing();
}
/** */
@@ -267,19 +133,7 @@
@Override
public void disable(int state1, int state2, boolean animate) {
- mView.disable(state2, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
- }
-
- private void startSettingsActivity() {
- ActivityLaunchAnimator.Controller animationController =
- mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView(
- mSettingsButtonContainer,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) : null;
- mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
- true /* dismissShade */, animationController);
- }
-
- private boolean isTunerEnabled() {
- return mTunerService.isTunerEnabled();
+ mView.disable(state2);
+ mQsFooterActionsController.disable(state2);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 2046550..2de2d04 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -28,6 +28,7 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter;
+import com.android.systemui.qs.QSFooterActionsView;
import com.android.systemui.qs.QSFooterView;
import com.android.systemui.qs.QSFooterViewController;
import com.android.systemui.qs.QSFragment;
@@ -122,6 +123,12 @@
/** */
@Provides
+ static QSFooterActionsView providesQSFooterActionsView(@RootView View view) {
+ return view.findViewById(R.id.qs_footer_actions_container);
+ }
+
+ /** */
+ @Provides
@QSScope
static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
qsFooterViewController.init();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index a63d1f8..e338750 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -287,14 +287,22 @@
}
showProgressBar();
setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular());
- setConnectedWifiLayout();
- boolean isWifiEnabled = mWifiManager.isWifiEnabled();
- mWiFiToggle.setChecked(isWifiEnabled);
- int visible = isWifiEnabled ? View.VISIBLE : View.GONE;
- mWifiRecyclerView.setVisibility(visible);
- mAdapter.notifyDataSetChanged();
- mSeeAllLayout.setVisibility(visible);
- mSpace.setVisibility(isWifiEnabled ? View.GONE : View.VISIBLE);
+
+ final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
+ final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+ updateWifiToggle(isWifiEnabled, isDeviceLocked);
+ updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+
+ List<WifiEntry> wifiEntryList = mInternetDialogController.getWifiEntryList();
+ final int wifiListVisibility =
+ (isDeviceLocked || wifiEntryList == null || wifiEntryList.size() <= 0)
+ ? View.GONE : View.VISIBLE;
+ mWifiRecyclerView.setVisibility(wifiListVisibility);
+ if (wifiListVisibility == View.VISIBLE) {
+ mAdapter.notifyDataSetChanged();
+ }
+ mSeeAllLayout.setVisibility(wifiListVisibility);
+ mSpace.setVisibility(wifiListVisibility == View.VISIBLE ? View.GONE : View.VISIBLE);
}
private void setOnClickListener() {
@@ -358,8 +366,14 @@
}
}
- private void setConnectedWifiLayout() {
- if (!mWifiManager.isWifiEnabled() || mConnectedWifiEntry == null) {
+ private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
+ mWiFiToggle.setChecked(isWifiEnabled);
+ mTurnWifiOnLayout.setBackground(
+ (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+ }
+
+ private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
+ if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
mConnectedWifListLayout.setBackground(null);
mConnectedWifListLayout.setVisibility(View.GONE);
return;
@@ -418,7 +432,8 @@
}
protected void showProgressBar() {
- if (mWifiManager == null || !mWifiManager.isWifiEnabled()) {
+ if (mWifiManager == null || !mWifiManager.isWifiEnabled()
+ || mInternetDialogController.isDeviceLocked()) {
setProgressBarVisible(false);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 890dcfd..80a93d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -67,6 +67,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.util.settings.GlobalSettings;
@@ -97,6 +98,8 @@
private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT =
R.string.tap_a_network_to_connect;
+ private static final int SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS =
+ R.string.unlock_to_view_networks;
private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS =
R.string.wifi_empty_list_wifi_on;
private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE =
@@ -137,6 +140,9 @@
@VisibleForTesting
protected WifiUtils.InternetIconInjector mWifiIconInjector;
+ @VisibleForTesting
+ KeyguardStateController mKeyguardStateController;
+
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -161,7 +167,7 @@
@Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
@Main Handler handler, @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings, KeyguardStateController keyguardStateController) {
if (DEBUG) {
Log.d(TAG, "Init InternetDialogController");
}
@@ -175,6 +181,7 @@
mSubscriptionManager = subscriptionManager;
mBroadcastDispatcher = broadcastDispatcher;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
mConnectionStateFilter = new IntentFilter();
mConnectionStateFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mConnectionStateFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
@@ -272,6 +279,15 @@
return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
}
+ if (isDeviceLocked()) {
+ // When the device is locked.
+ // Sub-Title: Unlock to view networks
+ if (DEBUG) {
+ Log.d(TAG, "The device is locked.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS);
+ }
+
final List<ScanResult> wifiList = mWifiManager.getScanResults();
if (wifiList != null && wifiList.size() != 0) {
return mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT);
@@ -683,6 +699,10 @@
&& serviceState.getState() == serviceState.STATE_IN_SERVICE;
}
+ public boolean isDeviceLocked() {
+ return !mKeyguardStateController.isUnlocked();
+ }
+
boolean activeNetworkIsCellular() {
if (mConnectivityManager == null) {
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 8f1a578..7548d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -26,6 +26,7 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.IActivityTaskManager;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -114,6 +115,8 @@
@VisibleForTesting
final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
private final KeyguardStateController mKeyguardStateController;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private final DevicePolicyManager mDevicePolicyManager;
protected final Handler mHandler;
private final ActivityStarter mActivityStarter;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -149,6 +152,8 @@
UserManager userManager,
UserTracker userTracker,
KeyguardStateController keyguardStateController,
+ DeviceProvisionedController deviceProvisionedController,
+ DevicePolicyManager devicePolicyManager,
@Main Handler handler,
ActivityStarter activityStarter,
BroadcastDispatcher broadcastDispatcher,
@@ -178,6 +183,8 @@
mGuestIsResetting = new AtomicBoolean();
mGuestCreationScheduled = new AtomicBoolean();
mKeyguardStateController = keyguardStateController;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mDevicePolicyManager = devicePolicyManager;
mHandler = handler;
mActivityStarter = activityStarter;
mUserManager = userManager;
@@ -733,10 +740,27 @@
}
/**
+ * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
+ * observer to wait until the device is provisioned, then schedule the guest creation.
+ */
+ public void schedulePostBootGuestCreation() {
+ if (isDeviceAllowedToAddGuest()) {
+ guaranteeGuestPresent();
+ } else {
+ mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
+ }
+ }
+
+ private boolean isDeviceAllowedToAddGuest() {
+ return mDeviceProvisionedController.isDeviceProvisioned()
+ && !mDevicePolicyManager.isDeviceManaged();
+ }
+
+ /**
* If there is no guest on the device, schedule creation of a new guest user in the background.
*/
- public void guaranteeGuestPresent() {
- if (mUserManager.findCurrentGuestUser() == null) {
+ private void guaranteeGuestPresent() {
+ if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
scheduleGuestCreation();
}
}
@@ -1056,6 +1080,21 @@
}
};
+ private final DeviceProvisionedController.DeviceProvisionedListener
+ mGuaranteeGuestPresentAfterProvisioned =
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ if (isDeviceAllowedToAddGuest()) {
+ mBgExecutor.execute(
+ () -> mDeviceProvisionedController.removeCallback(
+ mGuaranteeGuestPresentAfterProvisioned));
+ guaranteeGuestPresent();
+ }
+ }
+ };
+
+
private final class ExitGuestDialog extends SystemUIDialog implements
DialogInterface.OnClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index f317628..dbf115b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2255,6 +2255,11 @@
@Override
public void onClick(View view) {
+ // If the ringer drawer isn't open, don't let anything in it be clicked.
+ if (!mIsRingerDrawerOpen) {
+ return;
+ }
+
setRingerMode(mClickedRingerMode);
mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 7e733a9..7e3553a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -55,6 +55,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -213,6 +214,13 @@
}
//
+ // Freeform (optional feature)
+ //
+
+ @BindsOptionalOf
+ abstract Optional<FreeformTaskListener> optionalFreeformTaskListener();
+
+ //
// Hide display cutout
//
@@ -328,9 +336,11 @@
@WMSingleton
@Provides
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
- Context context, @ShellMainThread ShellExecutor mainExecutor,
+ DisplayController displayController, Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(organizer, pool, context, mainExecutor, animExecutor);
+ return new Transitions(organizer, pool, displayController, context, mainExecutor,
+ animExecutor);
}
//
@@ -451,6 +461,7 @@
Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Optional<Optional<FreeformTaskListener>> freeformTaskListener,
Transitions transitions,
StartingWindowController startingWindow,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -463,6 +474,7 @@
appPairsOptional,
pipTouchHandlerOptional,
fullscreenTaskListener,
+ freeformTaskListener,
transitions,
startingWindow,
mainExecutor);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index be7813e..6397ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -89,6 +90,18 @@
}
//
+ // Freeform
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<FreeformTaskListener> provideFreeformTaskListener(
+ Context context,
+ SyncTransactionQueue syncQueue) {
+ return Optional.ofNullable(FreeformTaskListener.create(context, syncQueue));
+ }
+
+ //
// Split/multiwindow
//
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
new file mode 100644
index 0000000..1a96178
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.FlagReaderPlugin;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class FeatureFlagsTest extends SysuiTestCase {
+
+ @Mock FeatureFlagReader mFeatureFlagReader;
+
+ private FeatureFlags mFeatureFlags;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFeatureFlags = new FeatureFlags(mFeatureFlagReader, getContext());
+ }
+
+ @Test
+ public void testAddListener() {
+ Flag<?> flag = new BooleanFlag(1);
+ mFeatureFlags.addFlag(flag);
+
+ // Assert and capture that a plugin listener was added.
+ ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
+
+ verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+ FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+
+ // Signal a change. No listeners, so no real effect.
+ pluginListener.onFlagChanged(flag.getId());
+
+ // Add a listener for the flag
+ final Flag<?>[] changedFlag = {null};
+ FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+ mFeatureFlags.addFlagListener(flag, listener);
+
+ // No changes seen yet.
+ assertThat(changedFlag[0]).isNull();
+
+ // Signal a change.
+ pluginListener.onFlagChanged(flag.getId());
+
+ // Assert that the change was for the correct flag.
+ assertThat(changedFlag[0]).isEqualTo(flag);
+ }
+
+ @Test
+ public void testRemoveListener() {
+ Flag<?> flag = new BooleanFlag(1);
+ mFeatureFlags.addFlag(flag);
+
+ // Assert and capture that a plugin listener was added.
+ ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
+
+ verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+ FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+
+ // Add a listener for the flag
+ final Flag<?>[] changedFlag = {null};
+ FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+ mFeatureFlags.addFlagListener(flag, listener);
+
+ // Signal a change.
+ pluginListener.onFlagChanged(flag.getId());
+
+ // Assert that the change was for the correct flag.
+ assertThat(changedFlag[0]).isEqualTo(flag);
+
+ changedFlag[0] = null;
+
+ // Now remove the listener.
+ mFeatureFlags.removeFlagListener(flag, listener);
+ // Signal a change.
+ pluginListener.onFlagChanged(flag.getId());
+ // Assert that the change was not triggered
+ assertThat(changedFlag[0]).isNull();
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index 79b0dd0..8e1b13c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -69,7 +69,7 @@
whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
.thenReturn(true)
whenever(mediaHost.hostView).thenReturn(hostView)
-
+ hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
keyguardMediaController = KeyguardMediaController(
mediaHost,
bypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 8fd2a32..3ea57be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -142,6 +142,8 @@
@Test
public void testCreateNavigationBarsIncludeDefaultTrue() {
+ // Tablets may be using taskbar and the logic is different
+ mNavigationBarController.mIsTablet = false;
doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
mNavigationBarController.createNavigationBars(true, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt
new file mode 100644
index 0000000..9378b2b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt
@@ -0,0 +1,91 @@
+package com.android.systemui.qs
+
+import com.android.systemui.R
+import android.os.UserManager
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.systemui.Dependency
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.utils.leaks.FakeTunerService
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class QSFooterActionsControllerTest : LeakCheckedTest() {
+ @Mock
+ private lateinit var userManager: UserManager
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock
+ private lateinit var userInfoController: UserInfoController
+ @Mock
+ private lateinit var qsPanelController: QSPanelController
+ @Mock
+ private lateinit var multiUserSwitchController: MultiUserSwitchController
+ @Mock
+ private lateinit var globalActionsDialog: GlobalActionsDialogLite
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
+ @Mock
+ private lateinit var controller: QSFooterActionsController
+
+ private val metricsLogger: MetricsLogger = FakeMetricsLogger()
+ private lateinit var view: QSFooterActionsView
+ private val falsingManager: FalsingManagerFake = FalsingManagerFake()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
+ val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
+
+ view = LayoutInflater.from(context)
+ .inflate(R.layout.qs_footer_actions, null) as QSFooterActionsView
+
+ controller = QSFooterActionsController(view, qsPanelController, activityStarter,
+ userManager, userInfoController, multiUserSwitchController,
+ deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
+ globalActionsDialog, uiEventLogger, showPMLiteButton = true)
+ controller.init()
+ controller.onViewAttached()
+ }
+
+ @Test
+ fun testLogPowerMenuClick() {
+ controller.expanded = true
+ falsingManager.setFalseTap(false)
+
+ view.findViewById<View>(R.id.pm_lite).performClick()
+ // Verify clicks are logged
+ verify(uiEventLogger, Mockito.times(1))
+ .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+ }
+
+ @Test
+ fun testSettings_UserNotSetup() {
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
+ view.findViewById<View>(R.id.settings_button).performClick()
+ // Verify Settings wasn't launched.
+ verify<ActivityStarter>(activityStarter, Mockito.never()).startActivity(any(), anyBoolean())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 6f7bf3b..8c6c358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -18,16 +18,11 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ClipData;
import android.content.ClipboardManager;
-import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -35,21 +30,8 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.globalactions.GlobalActionsDialogLite;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.MultiUserSwitchController;
-import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.utils.leaks.FakeTunerService;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -67,14 +49,6 @@
@Mock
private QSFooterView mView;
@Mock
- private UserManager mUserManager;
- @Mock
- private ActivityStarter mActivityStarter;
- @Mock
- private DeviceProvisionedController mDeviceProvisionedController;
- @Mock
- private UserInfoController mUserInfoController;
- @Mock
private UserTracker mUserTracker;
@Mock
private QSPanelController mQSPanelController;
@@ -82,36 +56,19 @@
private ClipboardManager mClipboardManager;
@Mock
private QuickQSPanelController mQuickQSPanelController;
- private FakeTunerService mFakeTunerService;
- private MetricsLogger mMetricsLogger = new FakeMetricsLogger();
- private FalsingManagerFake mFalsingManager;
-
- @Mock
- private SettingsButton mSettingsButton;
@Mock
private TextView mBuildText;
@Mock
- private View mEdit;
- @Mock
- private MultiUserSwitchController mMultiUserSwitchController;
- @Mock
- private View mPowerMenuLiteView;
- @Mock
- private GlobalActionsDialogLite mGlobalActionsDialog;
- @Mock
- private UiEventLogger mUiEventLogger;
+ private QSFooterActionsController mQSFooterActionsController;
private QSFooterViewController mController;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mFalsingManager = new FalsingManagerFake();
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
- mFakeTunerService = (FakeTunerService) Dependency.get(TunerService.class);
-
mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
when(mView.getContext()).thenReturn(mContext);
@@ -119,16 +76,10 @@
when(mUserTracker.getUserContext()).thenReturn(mContext);
when(mView.isAttachedToWindow()).thenReturn(true);
- when(mView.findViewById(R.id.settings_button)).thenReturn(mSettingsButton);
when(mView.findViewById(R.id.build)).thenReturn(mBuildText);
- when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit);
- when(mView.findViewById(R.id.pm_lite)).thenReturn(mPowerMenuLiteView);
- mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
- mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
- mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
- mMetricsLogger, mFalsingManager, false, mGlobalActionsDialog,
- mUiEventLogger);
+ mController = new QSFooterViewController(mView, mUserTracker, mQSPanelController,
+ mQuickQSPanelController, mQSFooterActionsController);
mController.init();
}
@@ -148,40 +99,4 @@
verify(mClipboardManager).setPrimaryClip(captor.capture());
assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text);
}
-
- @Test
- public void testSettings_UserNotSetup() {
- ArgumentCaptor<View.OnClickListener> onClickCaptor =
- ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mSettingsButton).setOnClickListener(onClickCaptor.capture());
-
- when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
-
- onClickCaptor.getValue().onClick(mSettingsButton);
- // Verify Settings wasn't launched.
- verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
- }
-
- @Test
- public void testLogPowerMenuClick() {
- // Enable power menu button
- mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
- mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
- mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
- mMetricsLogger, new FalsingManagerFake(), true, mGlobalActionsDialog,
- mUiEventLogger);
- mController.init();
- mController.setExpanded(true);
- mFalsingManager.setFalseTap(false);
-
- ArgumentCaptor<View.OnClickListener> onClickCaptor =
- ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mPowerMenuLiteView).setOnClickListener(onClickCaptor.capture());
-
- onClickCaptor.getValue().onClick(mPowerMenuLiteView);
-
- // Verify clicks are logged
- verify(mUiEventLogger, times(1))
- .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index f876a43..2b3624e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -33,13 +33,13 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.Utils;
import com.android.settingslib.wifi.WifiUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -79,7 +79,7 @@
@Mock
private GlobalSettings mGlobalSettings;
@Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private KeyguardStateController mKeyguardStateController;
@Mock
private NetworkController.AccessPointController mAccessPointController;
@Mock
@@ -99,15 +99,16 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
when(mWifiManager.getConnectionInfo()).thenReturn(mWifiInfo);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
when(mConnectedEntry.isDefaultNetwork()).thenReturn(true);
mInternetDialogController = new MockInternetDialogController(mContext,
mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
mSubscriptionManager, mTelephonyManager, mWifiManager,
mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
- mKeyguardUpdateMonitor, mGlobalSettings);
+ mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDialogController.mOnSubscriptionsChangedListener);
mInternetDialogController.onStart(
@@ -174,6 +175,16 @@
}
@Test
+ public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ getResourcesString("unlock_to_view_networks")));
+ }
+
+ @Test
public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
mInternetDialogController.setAirplaneModeEnabled(false);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
@@ -311,6 +322,20 @@
verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
}
+ @Test
+ public void isDeviceLocked_keyguardIsUnlocked_returnFalse() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isFalse();
+ }
+
+ @Test
+ public void isDeviceLocked_keyguardIsLocked_returnTrue() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isTrue();
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
@@ -331,10 +356,12 @@
@Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
@Main Handler handler, @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher,
- KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
+ KeyguardStateController keyguardStateController) {
super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
- broadcastDispatcher, keyguardUpdateMonitor, globalSettings);
+ broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
+ keyguardStateController);
mGlobalSettings = globalSettings;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 856e3a1..de2c7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -59,24 +59,31 @@
private static final String WIFI_TITLE = "Connected Wi-Fi Title";
private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary";
- private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
-
- private InternetDialogFactory mInternetDialogFactory = mock(InternetDialogFactory.class);
- private InternetAdapter mInternetAdapter = mock(InternetAdapter.class);
- private InternetDialogController mInternetDialogController = mock(
- InternetDialogController.class);
- private InternetDialogController.InternetDialogCallback mCallback =
- mock(InternetDialogController.InternetDialogCallback.class);
- private MockInternetDialog mInternetDialog;
- private WifiReceiver mWifiReceiver = null;
- private WifiManager mMockWifiManager = mock(WifiManager.class);
- private TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
@Mock
- private WifiEntry mWifiEntry = mock(WifiEntry.class);
+ private InternetDialogFactory mInternetDialogFactory;
@Mock
- private WifiInfo mWifiInfo;
+ private InternetDialogController mInternetDialogController;
+ @Mock
+ private UiEventLogger mUiEventLogger;
@Mock
private Handler mHandler;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private InternetAdapter mInternetAdapter;
+ @Mock
+ private WifiManager mMockWifiManager;
+ @Mock
+ private WifiEntry mWifiEntry;
+ @Mock
+ private WifiInfo mWifiInfo;
+
+ private MockInternetDialog mInternetDialog;
+ private WifiReceiver mWifiReceiver;
+ private LinearLayout mWifiToggle;
+ private LinearLayout mConnectedWifi;
+ private RecyclerView mWifiList;
+ private LinearLayout mSeeAll;
@Before
public void setUp() {
@@ -99,6 +106,10 @@
when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
when(mInternetDialogController.getWifiEntryList()).thenReturn(Arrays.asList(mWifiEntry));
+ mWifiToggle = mInternetDialog.mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+ mConnectedWifi = mInternetDialog.mDialogView.requireViewById(R.id.wifi_connected_layout);
+ mWifiList = mInternetDialog.mDialogView.requireViewById(R.id.wifi_list_layout);
+ mSeeAll = mInternetDialog.mDialogView.requireViewById(R.id.see_all_layout);
}
@After
@@ -137,7 +148,7 @@
}
@Test
- public void updateDialog_withWifiOnAndHasConnectedWifi_connectedWifiLayoutVisible() {
+ public void updateDialog_wifiOnAndHasConnectedWifi_showConnectedWifi() {
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
@@ -147,36 +158,85 @@
mInternetDialog.updateDialog();
- final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById(
- R.id.wifi_connected_layout);
- assertThat(linearLayout.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
- public void updateDialog_withWifiOnAndNoConnectedWifi_connectedWifiLayoutGone() {
+ public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() {
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
- mInternetDialog.updateDialog();
- final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById(
- R.id.wifi_connected_layout);
- assertThat(linearLayout.getVisibility()).isEqualTo(View.GONE);
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
}
@Test
- public void updateDialog_withWifiOff_WifiRecycleViewGone() {
- when(mMockWifiManager.isWifiEnabled()).thenReturn(false);
- mInternetDialog.updateDialog();
- final RecyclerView view = mInternetDialog.mDialogView.requireViewById(
- R.id.wifi_list_layout);
+ public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() {
+ when(mInternetDialogController.getWifiEntryList()).thenReturn(null);
- assertThat(view.getVisibility()).isEqualTo(View.GONE);
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
}
@Test
- public void onClickSeeMoreButton_clickSeeMore_verifyLaunchNetworkSetting() {
- final LinearLayout seeAllLayout = mInternetDialog.mDialogView.requireViewById(
- R.id.see_all_layout);
- seeAllLayout.performClick();
+ public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+ List<WifiEntry> wifiEntries = new ArrayList<WifiEntry>();
+ wifiEntries.add(mWifiEntry);
+ when(mInternetDialogController.getWifiEntryList()).thenReturn(wifiEntries);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasConnectedWifi_showHighlightWifiToggle() {
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+ when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+ when(mWifiEntry.isDefaultNetwork()).thenReturn(true);
+ mInternetDialog.mConnectedWifiEntry = mWifiEntry;
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiToggle.getBackground()).isNotNull();
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasConnectedWifi_hideConnectedWifi() {
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+ when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+ when(mWifiEntry.isDefaultNetwork()).thenReturn(true);
+ mInternetDialog.mConnectedWifiEntry = mWifiEntry;
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasWifiList_hideWifiListAndSeeAll() {
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+ List<WifiEntry> wifiEntries = new ArrayList<WifiEntry>();
+ wifiEntries.add(mWifiEntry);
+ when(mInternetDialogController.getWifiEntryList()).thenReturn(wifiEntries);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
+ mSeeAll.performClick();
verify(mInternetDialogController).launchNetworkSetting();
}
@@ -193,6 +253,17 @@
}
@Test
+ public void showProgressBar_deviceLocked_hideProgressBar() {
+ Mockito.reset(mHandler);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.showProgressBar();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ @Test
public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() {
Mockito.reset(mHandler);
when(mMockWifiManager.isWifiEnabled()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index c365ef2..b1b9ff4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy
import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
@@ -63,6 +64,8 @@
@SmallTest
class UserSwitcherControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var handler: Handler
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var userManager: UserManager
@@ -107,6 +110,8 @@
userManager,
userTracker,
keyguardStateController,
+ deviceProvisionedController,
+ devicePolicyManager,
handler,
activityStarter,
broadcastDispatcher,
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index cd332a6..4946ad4 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -125,7 +125,7 @@
// connect with remote AppPredictionService instead for dark launch
usesPeopleService = false;
}
- final boolean serviceExists = resolveService(sessionId, false,
+ final boolean serviceExists = resolveService(sessionId, true,
usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
final AppPredictionSessionInfo sessionInfo = new AppPredictionSessionInfo(
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a56b1db..5aec6aa 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3285,57 +3285,6 @@
}
}
- private void applyResourceOverlaysToWidgetsLocked(Set<String> packageNames, int userId,
- boolean updateFrameworkRes) {
- for (int i = 0, N = mProviders.size(); i < N; i++) {
- Provider provider = mProviders.get(i);
- if (provider.getUserId() != userId) {
- continue;
- }
-
- final String packageName = provider.id.componentName.getPackageName();
- if (!updateFrameworkRes && !packageNames.contains(packageName)) {
- continue;
- }
-
- ApplicationInfo newAppInfo = null;
- try {
- newAppInfo = mPackageManager.getApplicationInfo(packageName,
- PackageManager.GET_SHARED_LIBRARY_FILES, userId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to retrieve app info for " + packageName
- + " userId=" + userId, e);
- }
- if (newAppInfo == null) {
- continue;
- }
- ApplicationInfo oldAppInfo = provider.info.providerInfo.applicationInfo;
- if (!newAppInfo.sourceDir.equals(oldAppInfo.sourceDir)) {
- // Overlay paths are generated against a particular version of an application.
- // The overlays paths of a newly upgraded application are incompatible with the
- // old version of the application.
- continue;
- }
-
- // Isolate the changes relating to RROs. The app info must be copied to prevent
- // affecting other parts of system server that may have cached this app info.
- oldAppInfo = new ApplicationInfo(oldAppInfo);
- oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone();
- oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone();
- provider.info.providerInfo.applicationInfo = oldAppInfo;
-
- for (int j = 0, M = provider.widgets.size(); j < M; j++) {
- Widget widget = provider.widgets.get(j);
- if (widget.views != null) {
- widget.views.updateAppInfo(oldAppInfo);
- }
- if (widget.maskedViews != null) {
- widget.maskedViews.updateAppInfo(oldAppInfo);
- }
- }
- }
- }
-
/**
* Updates all providers with the specified package names, and records any providers that were
* pruned.
@@ -4926,14 +4875,5 @@
public void unlockUser(int userId) {
handleUserUnlocked(userId);
}
-
- @Override
- public void applyResourceOverlaysToWidgets(Set<String> packageNames, int userId,
- boolean updateFrameworkRes) {
- synchronized (mLock) {
- applyResourceOverlaysToWidgetsLocked(new HashSet<>(packageNames), userId,
- updateFrameworkRes);
- }
- }
}
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 0621d0f..e390ae28 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -353,6 +353,12 @@
/**
+ * Retrieve all receivers that can handle a broadcast of the given intent.
+ */
+ public abstract List<ResolveInfo> queryIntentReceivers(Intent intent,
+ String resolvedType, int flags, int filterCallingUid, int userId);
+
+ /**
* Retrieve all services that can be performed for the given intent.
* @see PackageManager#queryIntentServices(Intent, int)
*/
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 047aae7..aae1cc0 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -147,6 +147,47 @@
return true;
}
+ /**
+ * Returns whether an intent matches the IntentFilter with a pre-resolved type.
+ */
+ public static boolean intentMatchesFilter(
+ IntentFilter filter, Intent intent, String resolvedType) {
+ final boolean debug = localLOGV
+ || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+
+ final Printer logPrinter = debug
+ ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM) : null;
+
+ if (debug) {
+ Slog.v(TAG, "Intent: " + intent);
+ Slog.v(TAG, "Matching against filter: " + filter);
+ filter.dump(logPrinter, " ");
+ }
+
+ final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
+ intent.getData(), intent.getCategories(), TAG);
+
+ if (match >= 0) {
+ if (debug) {
+ Slog.v(TAG, "Filter matched! match=0x" + Integer.toHexString(match));
+ }
+ return true;
+ } else {
+ if (debug) {
+ final String reason;
+ switch (match) {
+ case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
+ case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
+ case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
+ case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
+ default: reason = "unknown reason"; break;
+ }
+ Slog.v(TAG, "Filter did not match: " + reason);
+ }
+ return false;
+ }
+ }
+
private ArrayList<F> collectFilters(F[] array, IntentFilter matching) {
ArrayList<F> res = null;
if (array != null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35ed477..0d8cbaf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -193,7 +193,6 @@
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetManagerInternal;
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
@@ -12628,76 +12627,72 @@
int pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
List<ResolveInfo> receivers = null;
- try {
- HashSet<ComponentName> singleUserReceivers = null;
- boolean scannedFirstReceivers = false;
- for (int user : users) {
- // Skip users that have Shell restrictions
- if (callingUid == SHELL_UID
- && mUserController.hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
- continue;
- }
- List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
- .queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
- if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
- // If this is not the system user, we need to check for
- // any receivers that should be filtered out.
- for (int i=0; i<newReceivers.size(); i++) {
- ResolveInfo ri = newReceivers.get(i);
- if ((ri.activityInfo.flags&ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
- newReceivers.remove(i);
- i--;
- }
+ HashSet<ComponentName> singleUserReceivers = null;
+ boolean scannedFirstReceivers = false;
+ for (int user : users) {
+ // Skip users that have Shell restrictions
+ if (callingUid == SHELL_UID
+ && mUserController.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+ continue;
+ }
+ List<ResolveInfo> newReceivers = mPackageManagerInt
+ .queryIntentReceivers(intent, resolvedType, pmFlags, callingUid, user);
+ if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
+ // If this is not the system user, we need to check for
+ // any receivers that should be filtered out.
+ for (int i = 0; i < newReceivers.size(); i++) {
+ ResolveInfo ri = newReceivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+ newReceivers.remove(i);
+ i--;
}
}
- if (newReceivers != null && newReceivers.size() == 0) {
- newReceivers = null;
- }
- if (receivers == null) {
- receivers = newReceivers;
- } else if (newReceivers != null) {
- // We need to concatenate the additional receivers
- // found with what we have do far. This would be easy,
- // but we also need to de-dup any receivers that are
- // singleUser.
- if (!scannedFirstReceivers) {
- // Collect any single user receivers we had already retrieved.
- scannedFirstReceivers = true;
- for (int i=0; i<receivers.size(); i++) {
- ResolveInfo ri = receivers.get(i);
- if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
- ComponentName cn = new ComponentName(
- ri.activityInfo.packageName, ri.activityInfo.name);
- if (singleUserReceivers == null) {
- singleUserReceivers = new HashSet<ComponentName>();
- }
- singleUserReceivers.add(cn);
- }
- }
- }
- // Add the new results to the existing results, tracking
- // and de-dupping single user receivers.
- for (int i=0; i<newReceivers.size(); i++) {
- ResolveInfo ri = newReceivers.get(i);
+ }
+ if (newReceivers != null && newReceivers.size() == 0) {
+ newReceivers = null;
+ }
+ if (receivers == null) {
+ receivers = newReceivers;
+ } else if (newReceivers != null) {
+ // We need to concatenate the additional receivers
+ // found with what we have do far. This would be easy,
+ // but we also need to de-dup any receivers that are
+ // singleUser.
+ if (!scannedFirstReceivers) {
+ // Collect any single user receivers we had already retrieved.
+ scannedFirstReceivers = true;
+ for (int i = 0; i < receivers.size(); i++) {
+ ResolveInfo ri = receivers.get(i);
if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
ComponentName cn = new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name);
if (singleUserReceivers == null) {
singleUserReceivers = new HashSet<ComponentName>();
}
- if (!singleUserReceivers.contains(cn)) {
- singleUserReceivers.add(cn);
- receivers.add(ri);
- }
- } else {
- receivers.add(ri);
+ singleUserReceivers.add(cn);
}
}
}
+ // Add the new results to the existing results, tracking
+ // and de-dupping single user receivers.
+ for (int i = 0; i < newReceivers.size(); i++) {
+ ResolveInfo ri = newReceivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ ComponentName cn = new ComponentName(
+ ri.activityInfo.packageName, ri.activityInfo.name);
+ if (singleUserReceivers == null) {
+ singleUserReceivers = new HashSet<ComponentName>();
+ }
+ if (!singleUserReceivers.contains(cn)) {
+ singleUserReceivers.add(cn);
+ receivers.add(ri);
+ }
+ } else {
+ receivers.add(ri);
+ }
+ }
}
- } catch (RemoteException ex) {
- // pm is in same process, this will never happen.
}
if (receivers != null && broadcastAllowList != null) {
for (int i = receivers.size() - 1; i >= 0; i--) {
@@ -13331,8 +13326,7 @@
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
// Need to resolve the intent to interested receivers...
- if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- == 0) {
+ if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
receivers = collectReceiverComponents(
intent, resolvedType, callingUid, users, broadcastAllowList);
}
@@ -16658,13 +16652,6 @@
if (updateFrameworkRes) {
ParsingPackageUtils.readConfigUseRoundIcon(null);
}
-
- AppWidgetManagerInternal widgets = LocalServices.getService(AppWidgetManagerInternal.class);
- if (widgets != null) {
- widgets.applyResourceOverlaysToWidgets(new HashSet<>(packagesToUpdate), userId,
- updateFrameworkRes);
- }
-
mProcessList.updateApplicationInfoLOSP(packagesToUpdate, userId, updateFrameworkRes);
if (updateFrameworkRes) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fcd198e..2f69abf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -738,6 +738,11 @@
private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
private long mLoweredFromNormalToVibrateTime;
+ // Uid of the active hotword detection service to check if caller is the one or not.
+ @GuardedBy("mHotwordDetectionServiceUidLock")
+ private int mHotwordDetectionServiceUid = android.os.Process.INVALID_UID;
+ private final Object mHotwordDetectionServiceUidLock = new Object();
+
// Array of Uids of valid accessibility services to check if caller is one of them
private final Object mAccessibilityServiceUidsLock = new Object();
@GuardedBy("mAccessibilityServiceUidsLock")
@@ -1339,6 +1344,9 @@
updateAssistantUId(true);
AudioSystem.setRttEnabled(mRttEnabled);
}
+ synchronized (mHotwordDetectionServiceUidLock) {
+ AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid);
+ }
synchronized (mAccessibilityServiceUidsLock) {
AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
}
@@ -9082,6 +9090,16 @@
}
@Override
+ public void setHotwordDetectionServiceUid(int uid) {
+ synchronized (mHotwordDetectionServiceUidLock) {
+ if (mHotwordDetectionServiceUid != uid) {
+ mHotwordDetectionServiceUid = uid;
+ AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid);
+ }
+ }
+ }
+
+ @Override
public void setAccessibilityServiceUids(IntArray uids) {
synchronized (mAccessibilityServiceUidsLock) {
if (uids.size() == 0) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 973fbd2..6d56780 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -367,6 +367,14 @@
}
/**
+ * Same as {@link AudioSystem#setHotwordDetectionServiceUid(int)}
+ * Communicate UID of current HotwordDetectionService to audio policy service.
+ */
+ public int setHotwordDetectionServiceUid(int uid) {
+ return AudioSystem.setHotwordDetectionServiceUid(uid);
+ }
+
+ /**
* Same as {@link AudioSystem#setCurrentImeUid(int)}
* Communicate UID of current InputMethodService to audio policy service.
*/
diff --git a/services/core/java/com/android/server/hdmi/RequestSadAction.java b/services/core/java/com/android/server/hdmi/RequestSadAction.java
new file mode 100644
index 0000000..4d36078
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/RequestSadAction.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Feature action that queries the Short Audio Descriptor (SAD) of another device. This action is
+ * initiated from the Android system working as TV device to get the SAD of the connected audio
+ * system device.
+ * <p>
+ * Package-private
+ */
+final class RequestSadAction extends HdmiCecFeatureAction {
+ private static final String TAG = "RequestSadAction";
+
+ // State in which the action is waiting for <Report Short Audio Descriptor>.
+ private static final int STATE_WAITING_FOR_REPORT_SAD = 1;
+
+ private static final List<Integer> ALL_CEC_CODECS = new ArrayList<Integer>(Arrays.asList(
+ Constants.AUDIO_CODEC_LPCM,
+ Constants.AUDIO_CODEC_DD,
+ Constants.AUDIO_CODEC_MPEG1,
+ Constants.AUDIO_CODEC_MP3,
+ Constants.AUDIO_CODEC_MPEG2,
+ Constants.AUDIO_CODEC_AAC,
+ Constants.AUDIO_CODEC_DTS,
+ Constants.AUDIO_CODEC_ATRAC,
+ Constants.AUDIO_CODEC_ONEBITAUDIO,
+ Constants.AUDIO_CODEC_DDP,
+ Constants.AUDIO_CODEC_DTSHD,
+ Constants.AUDIO_CODEC_TRUEHD,
+ Constants.AUDIO_CODEC_DST,
+ Constants.AUDIO_CODEC_WMAPRO,
+ Constants.AUDIO_CODEC_MAX));
+ private static final int MAX_SAD_PER_REQUEST = 4;
+ private static final int RETRY_COUNTER_MAX = 1;
+ private final int mTargetAddress;
+ private final RequestSadCallback mCallback;
+ // List of all valid SADs reported by the target device. Not parsed nor deduplicated.
+ private final List<byte[]> mSupportedSads = new ArrayList<>();
+ private int mQueriedSadCount = 0; // Number of SADs queries that has already been completed
+ private int mTimeoutRetry = 0; // Number of times we have already retried on time-out
+
+ /**
+ * Constructor.
+ *
+ * @param source an instance of {@link HdmiCecLocalDevice}.
+ * @param targetAddress the logical address the SAD is directed at.
+ */
+ RequestSadAction(HdmiCecLocalDevice source, int targetAddress, RequestSadCallback callback) {
+ super(source);
+ mTargetAddress = targetAddress;
+ mCallback = Objects.requireNonNull(callback);
+ }
+
+
+ @Override
+ boolean start() {
+ querySad();
+ return true;
+ }
+
+ private void querySad() {
+ if (mQueriedSadCount >= ALL_CEC_CODECS.size()) {
+ wrapUpAndFinish();
+ return;
+ }
+ int[] codecsToQuery = ALL_CEC_CODECS.subList(mQueriedSadCount,
+ Math.min(ALL_CEC_CODECS.size(), mQueriedSadCount + MAX_SAD_PER_REQUEST))
+ .stream().mapToInt(i -> i).toArray();
+ sendCommand(HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(getSourceAddress(),
+ mTargetAddress, codecsToQuery));
+ mState = STATE_WAITING_FOR_REPORT_SAD;
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WAITING_FOR_REPORT_SAD
+ || mTargetAddress != cmd.getSource()) {
+ return false;
+ }
+ if (cmd.getOpcode() == Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR) {
+ if (cmd.getParams() == null || cmd.getParams().length == 0
+ || cmd.getParams().length % 3 != 0) {
+ // Invalid message. Wait for time-out and query again.
+ return true;
+ }
+ for (int i = 0; i < cmd.getParams().length - 2; i += 3) {
+ if (isValidCodec(cmd.getParams()[i])) {
+ byte[] sad = new byte[]{cmd.getParams()[i], cmd.getParams()[i + 1],
+ cmd.getParams()[i + 2]};
+ updateResult(sad);
+ }
+ // Don't include invalid codecs in the result. Don't query again.
+ Slog.w(TAG, "Received invalid codec " + cmd.getParams()[i] + ".");
+ }
+ mQueriedSadCount += MAX_SAD_PER_REQUEST;
+ mTimeoutRetry = 0;
+ querySad();
+ return true;
+ }
+ if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT
+ && (cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR
+ && (cmd.getParams()[1] & 0xFF) == Constants.ABORT_INVALID_OPERAND) {
+ // Queried SADs are not supported
+ mQueriedSadCount += MAX_SAD_PER_REQUEST;
+ mTimeoutRetry = 0;
+ querySad();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isValidCodec(byte codec) {
+ return Constants.AUDIO_CODEC_NONE < (codec & 0xFF)
+ && (codec & 0xFF) <= Constants.AUDIO_CODEC_MAX;
+ }
+
+ private void updateResult(byte[] sad) {
+ mSupportedSads.add(sad);
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState != state) {
+ return;
+ }
+ if (state == STATE_WAITING_FOR_REPORT_SAD) {
+ if (++mTimeoutRetry <= RETRY_COUNTER_MAX) {
+ querySad();
+ return;
+ }
+ mQueriedSadCount += MAX_SAD_PER_REQUEST;
+ mTimeoutRetry = 0;
+ querySad();
+ }
+ }
+
+ private void wrapUpAndFinish() {
+ mCallback.onRequestSadDone(mSupportedSads);
+ finish();
+ }
+
+ /**
+ * Interface used to report result of SAD request.
+ */
+ interface RequestSadCallback {
+ /**
+ * Called when SAD request is done.
+ *
+ * @param sads a list of all supported SADs. It can be an empty list.
+ */
+ void onRequestSadDone(List<byte[]> supportedSads);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 14dc876..d2c15ab 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -289,6 +289,7 @@
import android.util.PackageUtils;
import android.util.Pair;
import android.util.PrintStreamPrinter;
+import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -327,6 +328,7 @@
import com.android.server.DeviceIdleInternal;
import com.android.server.EventLogTags;
import com.android.server.FgThread;
+import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.PackageWatchdog;
@@ -701,6 +703,20 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
+ /**
+ * Components of apps targeting Android T and above will stop receiving intents from
+ * external callers that do not match its declared intent filters.
+ *
+ * When an app registers an exported component in its manifest and adds an <intent-filter>,
+ * the component can be started by any intent - even those that do not match the intent filter.
+ * This has proven to be something that many developers find counterintuitive.
+ * Without checking the intent when the component is started, in some circumstances this can
+ * allow 3P apps to trigger internal-only functionality.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+ private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
+
public static final String PLATFORM_PACKAGE_NAME = "android";
static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
@@ -2160,9 +2176,11 @@
false /* requireFullPermission */, false /* checkShell */,
"query intent activities");
final String pkgName = intent.getPackage();
+ Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
+ originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
@@ -2172,8 +2190,9 @@
comp != null || pkgName != null /*onlyExposedExplicitly*/,
isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
flags));
+ List<ResolveInfo> list = Collections.emptyList();
+ boolean skipPostResolution = false;
if (comp != null) {
- final List<ResolveInfo> list = new ArrayList<>(1);
final ActivityInfo ai = getActivityInfo(comp, flags, userId);
if (ai != null) {
// When specifying an explicit component, we prevent the activity from being
@@ -2216,35 +2235,45 @@
if (!blockInstantResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
+ list = new ArrayList<>(1);
list.add(ri);
+ applyEnforceIntentFilterMatching(
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, intent, resolvedType, filterCallingUid);
}
}
-
- return applyPostResolutionFilter(
- list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
- resolveForStart,
- userId, intent);
+ } else {
+ QueryIntentActivitiesResult lockedResult =
+ queryIntentActivitiesInternalBody(
+ intent, resolvedType, flags, filterCallingUid, userId,
+ resolveForStart, allowDynamicSplits, pkgName, instantAppPkgName);
+ if (lockedResult.answer != null) {
+ skipPostResolution = true;
+ list = lockedResult.answer;
+ } else {
+ if (lockedResult.addInstant) {
+ String callingPkgName = getInstantAppPackageName(filterCallingUid);
+ boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
+ lockedResult.result = maybeAddInstantAppInstaller(
+ lockedResult.result, intent, resolvedType, flags,
+ userId, resolveForStart, isRequesterInstantApp);
+ }
+ if (lockedResult.sortResult) {
+ lockedResult.result.sort(RESOLVE_PRIORITY_SORTER);
+ }
+ list = lockedResult.result;
+ }
}
- QueryIntentActivitiesResult lockedResult =
- queryIntentActivitiesInternalBody(
- intent, resolvedType, flags, filterCallingUid, userId, resolveForStart,
- allowDynamicSplits, pkgName, instantAppPkgName);
- if (lockedResult.answer != null) {
- return lockedResult.answer;
+ if (originalIntent != null) {
+ // We also have to ensure all components match the original intent
+ applyEnforceIntentFilterMatching(
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, originalIntent, resolvedType, filterCallingUid);
}
- if (lockedResult.addInstant) {
- String callingPkgName = getInstantAppPackageName(filterCallingUid);
- boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
- lockedResult.result = maybeAddInstantAppInstaller(lockedResult.result, intent,
- resolvedType, flags, userId, resolveForStart, isRequesterInstantApp);
- }
- if (lockedResult.sortResult) {
- Collections.sort(lockedResult.result, RESOLVE_PRIORITY_SORTER);
- }
- return applyPostResolutionFilter(
- lockedResult.result, instantAppPkgName, allowDynamicSplits, filterCallingUid,
+ return skipPostResolution ? list : applyPostResolutionFilter(
+ list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
resolveForStart, userId, intent);
}
@@ -2267,15 +2296,17 @@
final String instantAppPkgName = getInstantAppPackageName(callingUid);
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
+ Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
+ originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
}
+ List<ResolveInfo> list = Collections.emptyList();
if (comp != null) {
- final List<ResolveInfo> list = new ArrayList<>(1);
final ServiceInfo si = getServiceInfo(comp, flags, userId);
if (si != null) {
// When specifying an explicit component, we prevent the service from being
@@ -2308,14 +2339,26 @@
if (!blockInstantResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.serviceInfo = si;
+ list = new ArrayList<>(1);
list.add(ri);
+ applyEnforceIntentFilterMatching(
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, intent, resolvedType, callingUid);
}
}
- return list;
+ } else {
+ list = queryIntentServicesInternalBody(intent, resolvedType, flags,
+ userId, callingUid, instantAppPkgName);
}
- return queryIntentServicesInternalBody(intent, resolvedType, flags,
- userId, callingUid, instantAppPkgName);
+ if (originalIntent != null) {
+ // We also have to ensure all components match the original intent
+ applyEnforceIntentFilterMatching(
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, originalIntent, resolvedType, callingUid);
+ }
+
+ return list;
}
protected @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
@@ -5544,10 +5587,8 @@
}
}
}
- private static final ThreadLocal<ThreadComputer> sThreadComputer = new ThreadLocal<>() {
- @Override protected ThreadComputer initialValue() {
- return new ThreadComputer();
- }};
+ private static final ThreadLocal<ThreadComputer> sThreadComputer =
+ ThreadLocal.withInitial(ThreadComputer::new);
/**
* This lock is used to make reads from {@link #sSnapshotInvalid} and
@@ -8136,7 +8177,7 @@
final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+ UserHandle.USER_SYSTEM, Binder.getCallingUid());
if (matches.size() == 1) {
return matches.get(0).getComponentInfo().packageName;
} else if (matches.size() == 0) {
@@ -8232,7 +8273,7 @@
final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+ UserHandle.USER_SYSTEM, Binder.getCallingUid());
ResolveInfo best = null;
final int N = matches.size();
for (int i = 0; i < N; i++) {
@@ -8260,7 +8301,7 @@
Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
List<ResolveInfo> matches = queryIntentReceiversInternal(intent, null,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+ UserHandle.USER_SYSTEM, Binder.getCallingUid());
ResolveInfo best = null;
final int N = matches.size();
for (int i = 0; i < N; i++) {
@@ -10939,30 +10980,32 @@
@Override
public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
String resolvedType, int flags, int userId) {
- return new ParceledListSlice<>(
- queryIntentReceiversInternal(intent, resolvedType, flags, userId,
- false /*allowDynamicSplits*/));
+ return new ParceledListSlice<>(queryIntentReceiversInternal(intent, resolvedType,
+ flags, userId, Binder.getCallingUid()));
}
+ // In this method, we have to know the actual calling UID, but in some cases Binder's
+ // call identity is removed, so the UID has to be passed in explicitly.
private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
- String resolvedType, int flags, int userId, boolean allowDynamicSplits) {
+ String resolvedType, int flags, int userId, int filterCallingUid) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
- final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+ enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
false /*checkShell*/, "query intent receivers");
- final String instantAppPkgName = getInstantAppPackageName(callingUid);
- flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+ final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
+ flags = updateFlagsForResolve(flags, userId, filterCallingUid, false /*includeInstantApps*/,
isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
flags));
+ Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
+ originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
}
+ List<ResolveInfo> list = Collections.emptyList();
if (comp != null) {
- final List<ResolveInfo> list = new ArrayList<>(1);
final ActivityInfo ai = getReceiverInfo(comp, flags, userId);
if (ai != null) {
// When specifying an explicit component, we prevent the activity from being
@@ -10998,40 +11041,44 @@
if (!blockResolution) {
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
+ list = new ArrayList<>(1);
list.add(ri);
+ applyEnforceIntentFilterMatching(
+ mInjector.getCompatibility(), mComponentResolver,
+ list, true, intent, resolvedType, filterCallingUid);
}
}
- return applyPostResolutionFilter(
- list, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
- intent);
+ } else {
+ // reader
+ synchronized (mLock) {
+ String pkgName = intent.getPackage();
+ if (pkgName == null) {
+ final List<ResolveInfo> result =
+ mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
+ if (result != null) {
+ list = result;
+ }
+ }
+ final AndroidPackage pkg = mPackages.get(pkgName);
+ if (pkg != null) {
+ final List<ResolveInfo> result = mComponentResolver.queryReceivers(
+ intent, resolvedType, flags, pkg.getReceivers(), userId);
+ if (result != null) {
+ list = result;
+ }
+ }
+ }
}
- // reader
- synchronized (mLock) {
- String pkgName = intent.getPackage();
- if (pkgName == null) {
- final List<ResolveInfo> result =
- mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
- if (result == null) {
- return Collections.emptyList();
- }
- return applyPostResolutionFilter(
- result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
- intent);
- }
- final AndroidPackage pkg = mPackages.get(pkgName);
- if (pkg != null) {
- final List<ResolveInfo> result = mComponentResolver.queryReceivers(
- intent, resolvedType, flags, pkg.getReceivers(), userId);
- if (result == null) {
- return Collections.emptyList();
- }
- return applyPostResolutionFilter(
- result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
- intent);
- }
- return Collections.emptyList();
+ if (originalIntent != null) {
+ // We also have to ensure all components match the original intent
+ applyEnforceIntentFilterMatching(
+ mInjector.getCompatibility(), mComponentResolver,
+ list, true, originalIntent, resolvedType, filterCallingUid);
}
+
+ return applyPostResolutionFilter(
+ list, instantAppPkgName, false, filterCallingUid, false, userId, intent);
}
@Override
@@ -11073,6 +11120,68 @@
includeInstantApps);
}
+ // Static to give access to ComputeEngine
+ private static void applyEnforceIntentFilterMatching(
+ PlatformCompat compat, ComponentResolver resolver,
+ List<ResolveInfo> resolveInfos, boolean isReceiver,
+ Intent intent, String resolvedType, int filterCallingUid) {
+ // Do not enforce filter matching when the caller is system or root.
+ // see ActivityManager#checkComponentPermission(String, int, int, boolean)
+ if (filterCallingUid == Process.ROOT_UID || filterCallingUid == Process.SYSTEM_UID) {
+ return;
+ }
+
+ final Printer logPrinter = DEBUG_INTENT_MATCHING
+ ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
+ : null;
+
+ for (int i = resolveInfos.size() - 1; i >= 0; --i) {
+ final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
+
+ // Do not enforce filter matching when the caller is the same app
+ if (info.applicationInfo.uid == filterCallingUid) {
+ continue;
+ }
+
+ // Only enforce filter matching if target app's target SDK >= T
+ if (!compat.isChangeEnabledInternal(
+ ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
+ continue;
+ }
+
+ final ParsedMainComponent comp;
+ if (info instanceof ActivityInfo) {
+ if (isReceiver) {
+ comp = resolver.getReceiver(info.getComponentName());
+ } else {
+ comp = resolver.getActivity(info.getComponentName());
+ }
+ } else if (info instanceof ServiceInfo) {
+ comp = resolver.getService(info.getComponentName());
+ } else {
+ // This shall never happen
+ throw new IllegalArgumentException("Unsupported component type");
+ }
+
+ if (comp.getIntents().isEmpty()) {
+ continue;
+ }
+
+ final boolean match = comp.getIntents().stream().anyMatch(
+ f -> IntentResolver.intentMatchesFilter(f, intent, resolvedType));
+ if (!match) {
+ Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
+ Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+ if (DEBUG_INTENT_MATCHING) {
+ Slog.v(TAG, "Component intent filters:");
+ comp.getIntents().forEach(f -> f.dump(logPrinter, " "));
+ Slog.v(TAG, "-----------------------------");
+ }
+ resolveInfos.remove(i);
+ }
+ }
+ }
+
@Override
public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent,
String resolvedType, int flags, int userId) {
@@ -23412,6 +23521,13 @@
}
@Override
+ public List<ResolveInfo> queryIntentReceivers(Intent intent,
+ String resolvedType, int flags, int filterCallingUid, int userId) {
+ return PackageManagerService.this.queryIntentReceiversInternal(intent, resolvedType,
+ flags, userId, filterCallingUid);
+ }
+
+ @Override
public List<ResolveInfo> queryIntentServices(
Intent intent, int flags, int callingUid, int userId) {
final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 3eb4bde..2fcc4b2 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -55,6 +55,7 @@
*/
public final class PackageImpl extends ParsingPackageImpl implements ParsedPackage, AndroidPackage {
+ @NonNull
public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
@NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) {
return new PackageImpl(packageName, baseCodePath, codePath, manifestArray, isCoreApp);
@@ -70,6 +71,7 @@
* this case only cares about
* volumeUuid, just fake it rather than having separate method paths.
*/
+ @NonNull
public static AndroidPackage buildFakeForDeletion(String packageName, String volumeUuid) {
return ((ParsedPackage) PackageImpl.forTesting(packageName)
.setVolumeUuid(volumeUuid)
@@ -77,11 +79,13 @@
.hideAsFinal();
}
+ @NonNull
@VisibleForTesting
public static ParsingPackage forTesting(String packageName) {
return forTesting(packageName, "");
}
+ @NonNull
@VisibleForTesting
public static ParsingPackage forTesting(String packageName, String baseCodePath) {
return new PackageImpl(packageName, baseCodePath, baseCodePath, null, false);
@@ -568,6 +572,7 @@
assignDerivedFields();
}
+ @NonNull
public static final Creator<PackageImpl> CREATOR = new Creator<PackageImpl>() {
@Override
public PackageImpl createFromParcel(Parcel source) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e6e18fd..37003ad 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3687,7 +3687,7 @@
@Override
void removeImmediately() {
- if (!finishing) {
+ if (!isState(DESTROYING, DESTROYED)) {
// If Task#removeImmediately is called directly with alive activities, ensure that the
// activities are destroyed and detached from process.
destroyImmediately("removeImmediately");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 25827cf..19cf14f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1908,15 +1908,15 @@
if (r.moveFocusableActivityToTop("setFocusedTask")) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
- } else if (touchedActivity != null && touchedActivity != r
- && touchedActivity.getTask() == r.getTask()
- && touchedActivity.getTaskFragment() != r.getTaskFragment()) {
- // Set the focused app directly since the focused window is not on the
- // top-most TaskFragment of the top-most Task
- final DisplayContent displayContent = touchedActivity.getDisplayContent();
- displayContent.setFocusedApp(touchedActivity);
- mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
- true /* updateInputWindows */);
+ } else if (touchedActivity != null && touchedActivity.isFocusable()) {
+ final TaskFragment parent = touchedActivity.getTaskFragment();
+ if (parent != null && parent.isEmbedded()) {
+ // Set the focused app directly if the focused window is currently embedded
+ final DisplayContent displayContent = touchedActivity.getDisplayContent();
+ displayContent.setFocusedApp(touchedActivity);
+ mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /* updateInputWindows */);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b2e7b8f..31f943b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -771,10 +771,19 @@
return true;
}
- if (focusedApp.getTask() == activity.getTask()
- && focusedApp.getTaskFragment() != activity.getTaskFragment()) {
- // Do not use the activity window of another TaskFragment in the same leaf Task
- return false;
+ // If the candidate activity is currently being embedded in the focused task, the
+ // activity cannot be focused unless it is on the same TaskFragment as the focusedApp's.
+ TaskFragment parent = activity.getTaskFragment();
+ if (parent != null && parent.isEmbedded()) {
+ Task hostTask = focusedApp.getTask();
+ if (hostTask.isEmbedded()) {
+ // Use the hosting task if the current task is embedded.
+ hostTask = hostTask.getParent().asTaskFragment().getTask();
+ }
+ if (activity.isDescendantOf(hostTask)
+ && activity.getTaskFragment() != focusedApp.getTaskFragment()) {
+ return false;
+ }
}
}
@@ -4577,7 +4586,9 @@
return true;
}
- if (task.isOrganized()) {
+ // TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove
+ // the special case when it does.
+ if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index eb5ab83..3bb72b2 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -306,7 +306,10 @@
boolean useSurfaceCrop = false;
final Task task = w.getTask();
if (task != null) {
- if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ // TODO(b/165794636): Remove the special case for freeform window once drag resizing is
+ // handled by WM shell.
+ if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+ && !task.inFreeformWindowingMode()) {
// If the window is in a TaskManaged by a TaskOrganizer then most cropping will
// be applied using the SurfaceControl hierarchy from the Organizer. This means
// we need to make sure that these changes in crop are reflected in the input
@@ -538,7 +541,8 @@
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
mRecentsAnimationInputConsumer.mWindowHandle)) {
- mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord);
+ mRecentsAnimationInputConsumer.show(mInputTransaction,
+ recentsAnimationController.getHighestLayerTask());
mAddRecentsAnimationInputConsumerHandle = false;
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 895a82b..53af563 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -30,10 +30,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.StatusBarManager;
@@ -222,7 +218,7 @@
InsetsState getInsetsForWindow(WindowState target) {
final InsetsState originalState = mStateController.getInsetsForWindow(target);
final InsetsState state = adjustVisibilityForTransientTypes(originalState);
- return adjustVisibilityForIme(target, state, state == originalState);
+ return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state;
}
/**
@@ -252,38 +248,16 @@
return state;
}
- private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
+ // Navigation bar insets is always visible to IME.
+ private static InsetsState adjustVisibilityForIme(InsetsState originalState,
boolean copyState) {
- if (w.mIsImWindow) {
- // Navigation bar insets is always visible to IME.
- final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
- if (originalNavSource != null && !originalNavSource.isVisible()) {
- final InsetsState state = copyState ? new InsetsState(originalState)
- : originalState;
- final InsetsSource navSource = new InsetsSource(originalNavSource);
- navSource.setVisible(true);
- state.addSource(navSource);
- return state;
- }
- } else if (w.mActivityRecord != null && !w.mActivityRecord.mLastImeShown) {
- // During switching tasks with gestural navigation, if the IME is attached to
- // one app window on that time, even the next app window is behind the IME window,
- // conceptually the window should not receive the IME insets if the next window is
- // not eligible IME requester and ready to show IME on top of it.
- final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp();
- final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
-
- if (originalImeSource != null && shouldImeAttachedToApp
- && (w.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS)
- || !w.getRequestedVisibility(ITYPE_IME))) {
- final InsetsState state = copyState ? new InsetsState(originalState)
- : originalState;
-
- final InsetsSource imeSource = new InsetsSource(originalImeSource);
- imeSource.setVisible(false);
- state.addSource(imeSource);
- return state;
- }
+ final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
+ if (originalNavSource != null && !originalNavSource.isVisible()) {
+ final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
+ final InsetsSource navSource = new InsetsSource(originalNavSource);
+ navSource.setVisible(true);
+ state.addSource(navSource);
+ return state;
}
return originalState;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 4f93c7a..c457df9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -1102,6 +1102,23 @@
return mTargetActivityRecord.findMainWindow();
}
+ /**
+ * Returns the task with the highest layer, or null if none is found.
+ */
+ public Task getHighestLayerTask() {
+ int highestLayer = Integer.MIN_VALUE;
+ Task highestLayerTask = null;
+ for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+ TaskAnimationAdapter adapter = mPendingAnimations.get(i);
+ int layer = adapter.mTask.getPrefixOrderIndex();
+ if (layer > highestLayer) {
+ highestLayer = layer;
+ highestLayerTask = adapter.mTask;
+ }
+ }
+ return highestLayerTask;
+ }
+
boolean isAnimatingTask(Task task) {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
if (task == mPendingAnimations.get(i).mTask) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 69ad59a..fec7ede 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3252,7 +3252,7 @@
boolean consumed = false;
if (traverseTopToBottom) {
for (int i = task.mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer child = mChildren.get(i);
+ final WindowContainer child = task.mChildren.get(i);
if (child.asTaskFragment() != null) {
child.forAllLeafTaskFragments(callback, traverseTopToBottom);
} else if (child.asActivityRecord() != null && !consumed) {
@@ -3262,7 +3262,7 @@
}
} else {
for (int i = 0; i < task.mChildren.size(); i++) {
- final WindowContainer child = mChildren.get(i);
+ final WindowContainer child = task.mChildren.get(i);
if (child.asTaskFragment() != null) {
child.forAllLeafTaskFragments(callback, traverseTopToBottom);
} else if (child.asActivityRecord() != null && !consumed) {
@@ -4174,8 +4174,7 @@
}
private boolean canBeOrganized() {
- if (mForceNotOrganized || !mAtmService.mTaskOrganizerController
- .isSupportedWindowingMode(getWindowingMode())) {
+ if (mForceNotOrganized) {
return false;
}
// All root tasks can be organized
@@ -4332,7 +4331,7 @@
final int windowingMode = getWindowingMode();
final TaskOrganizerController controller = mWmService.mAtmService.mTaskOrganizerController;
- final ITaskOrganizer organizer = controller.getTaskOrganizer(windowingMode);
+ final ITaskOrganizer organizer = controller.getTaskOrganizer();
if (!forceUpdate && mTaskOrganizer == organizer) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 99f31ad..b17a939 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,9 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
@@ -67,14 +64,6 @@
class TaskOrganizerController extends ITaskOrganizerController.Stub {
private static final String TAG = "TaskOrganizerController";
- // The set of modes that are currently supports
- // TODO: Remove once the task organizer can support all modes
- @VisibleForTesting
- static final int[] UNSUPPORTED_WINDOWING_MODES = {
- WINDOWING_MODE_UNDEFINED,
- WINDOWING_MODE_FREEFORM
- };
-
private class DeathRecipient implements IBinder.DeathRecipient {
ITaskOrganizer mTaskOrganizer;
@@ -373,11 +362,6 @@
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
mService.mRootWindowContainer.forAllTasks((task) -> {
- if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES,
- task.getWindowingMode())) {
- return;
- }
-
boolean returnTask = !task.mCreatedByOrganizer;
task.updateTaskOrganizerState(true /* forceUpdate */,
returnTask /* skipTaskAppeared */);
@@ -433,14 +417,8 @@
/**
* @return the task organizer key for a given windowing mode.
*/
- ITaskOrganizer getTaskOrganizer(int windowingMode) {
- return isSupportedWindowingMode(windowingMode)
- ? mTaskOrganizers.peekLast()
- : null;
- }
-
- boolean isSupportedWindowingMode(int winMode) {
- return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
+ ITaskOrganizer getTaskOrganizer() {
+ return mTaskOrganizers.peekLast();
}
// Capture the animation surface control for activity's main window
@@ -993,9 +971,6 @@
for (int k = 0; k < tasks.size(); k++) {
final Task task = tasks.get(k);
final int mode = task.getWindowingMode();
- if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, mode)) {
- continue;
- }
pw.println(innerPrefix + " ("
+ WindowConfiguration.windowingModeToString(mode) + ") " + task);
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a55fc4e..da16de7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -20,6 +20,8 @@
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
@@ -36,6 +38,7 @@
import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -1066,6 +1069,7 @@
final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
task.fillTaskInfo(tinfo);
change.setTaskInfo(tinfo);
+ change.setRotationAnimation(getTaskRotationAnimation(task));
}
out.addChange(change);
}
@@ -1073,6 +1077,23 @@
return out;
}
+ private static int getTaskRotationAnimation(@NonNull Task task) {
+ final ActivityRecord top = task.getTopVisibleActivity();
+ if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
+ final WindowState mainWin = top.findMainWindow(false);
+ if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
+ int anim = mainWin.getRotationAnimationHint();
+ if (anim >= 0) return anim;
+ anim = mainWin.getAttrs().rotationAnimation;
+ if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
+ if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
+ || !top.matchParentBounds()) {
+ // At the moment, we only support seamless rotation if there is only one window showing.
+ return ROTATION_ANIMATION_UNSPECIFIED;
+ }
+ return mainWin.getAttrs().rotationAnimation;
+ }
+
boolean getLegacyIsReady() {
return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId);
}
@@ -1166,6 +1187,9 @@
final DisplayContent dc = wc.asDisplayContent();
if (dc != null) {
flags |= FLAG_IS_DISPLAY;
+ if (dc.hasAlertWindowSurfaces()) {
+ flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+ }
}
if (isWallpaper(wc)) {
flags |= FLAG_IS_WALLPAPER;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index be8b21c..f641d21 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -648,7 +648,6 @@
private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
- private EnterpriseSpecificIdCalculator mEsidCalculator;
/**
* Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
@@ -1474,10 +1473,6 @@
return new LockPatternUtils(mContext);
}
- EnterpriseSpecificIdCalculator newEnterpriseSpecificIdCalculator() {
- return new EnterpriseSpecificIdCalculator(mContext);
- }
-
boolean storageManagerIsFileBasedEncryptionEnabled() {
return StorageManager.isFileEncryptedNativeOnly();
}
@@ -3126,10 +3121,6 @@
factoryResetIfDelayedEarlier();
ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
-
- // This is constructed here as EnterpriseSpecificIdCalculator depends on telephony
- // and wifi service and these services are only fully available at this stage.
- mEsidCalculator = mInjector.newEnterpriseSpecificIdCalculator();
break;
}
}
@@ -16927,8 +16918,6 @@
@Override
public void setOrganizationIdForUser(
@NonNull String callerPackage, @NonNull String organizationId, int userId) {
- Preconditions.checkState(mEsidCalculator != null,
- "setOrganizationIdForUser can't be called before boot phase completion");
if (!mHasFeature) {
return;
}
@@ -16962,7 +16951,10 @@
+ "be changed");
final String dpcPackage = owner.info.getPackageName();
mInjector.binderWithCleanCallingIdentity(() -> {
- final String esid = mEsidCalculator.calculateEnterpriseId(dpcPackage,
+ EnterpriseSpecificIdCalculator esidCalculator =
+ new EnterpriseSpecificIdCalculator(mContext);
+
+ final String esid = esidCalculator.calculateEnterpriseId(dpcPackage,
organizationId);
owner.mOrganizationId = organizationId;
owner.mEnrollmentSpecificId = esid;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 29091ce..df7f308 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -51,19 +51,13 @@
EnterpriseSpecificIdCalculator(Context context) {
TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
- if (telephonyService != null) {
- mImei = telephonyService.getImei(0);
- mMeid = telephonyService.getMeid(0);
- } else {
- mImei = "";
- mMeid = "";
- }
+ Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
+ mImei = telephonyService.getImei(0);
+ mMeid = telephonyService.getMeid(0);
mSerialNumber = Build.getSerial();
WifiManager wifiManager = context.getSystemService(WifiManager.class);
- String[] macAddresses = null;
- if (wifiManager != null) {
- macAddresses = wifiManager.getFactoryMacAddresses();
- }
+ Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
+ final String[] macAddresses = wifiManager.getFactoryMacAddresses();
if (macAddresses == null || macAddresses.length == 0) {
mMacAddress = "";
} else {
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 988c02b..1bcc3d1 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -32,6 +32,7 @@
"androidx.test.runner",
"junit",
"kotlin-test",
+ "kotlin-reflect",
"services.core",
"servicestests-utils",
"truth-prebuilt",
diff --git a/services/tests/PackageManagerServiceTests/unit/TEST_MAPPING b/services/tests/PackageManagerServiceTests/unit/TEST_MAPPING
new file mode 100644
index 0000000..cacfcf0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "PackageManagerServiceUnitTests"
+ }
+ ]
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
new file mode 100644
index 0000000..a7644ec
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.ConfigurationInfo
+import android.content.pm.FeatureGroupInfo
+import android.content.pm.FeatureInfo
+import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
+import android.content.pm.parsing.ParsingPackage
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedAttribution
+import android.content.pm.parsing.component.ParsedComponent
+import android.content.pm.parsing.component.ParsedInstrumentation
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.parsing.component.ParsedPermission
+import android.content.pm.parsing.component.ParsedPermissionGroup
+import android.content.pm.parsing.component.ParsedProcess
+import android.content.pm.parsing.component.ParsedProvider
+import android.content.pm.parsing.component.ParsedService
+import android.content.pm.parsing.component.ParsedUsesPermission
+import android.net.Uri
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.ArraySet
+import android.util.SparseArray
+import android.util.SparseIntArray
+import com.android.internal.R
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.parsing.pkg.PackageImpl
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import java.security.KeyPairGenerator
+import java.security.PublicKey
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
+
+ override val defaultImpl = PackageImpl.forTesting("com.example.test")
+ override val creator = PackageImpl.CREATOR
+
+ override val excludedMethods = listOf(
+ // Internal methods
+ "toAppInfoToString",
+ "toAppInfoWithoutState",
+ "toAppInfoWithoutStateWithoutFlags",
+ "assignDerivedFields",
+ "buildFakeForDeletion",
+ "capPermissionPriorities",
+ "forParsing",
+ "forTesting",
+ "getBaseAppDataCredentialProtectedDirForSystemUser",
+ "getBaseAppDataDeviceProtectedDirForSystemUser",
+ "getBoolean",
+ "setBoolean",
+ "hideAsFinal",
+ "hideAsParsed",
+ "markNotActivitiesAsNotExportedIfSingleUser",
+ "sortActivities",
+ "sortReceivers",
+ "sortServices",
+ "setAllComponentsDirectBootAware",
+ // Tested through setting minor/major manually
+ "setLongVersionCode",
+ "getLongVersionCode",
+ // Tested through constructor
+ "getManifestPackageName",
+ "setManifestPackageName",
+ // Utility methods
+ "getStorageUuid",
+ // Removal not tested, irrelevant for parcelling concerns
+ "removeUsesOptionalLibrary",
+ "clearAdoptPermissions",
+ "clearOriginalPackages",
+ "clearProtectedBroadcasts",
+ "removePermission",
+ "removeUsesLibrary",
+ "removeUsesOptionalNativeLibrary",
+ // Tested manually
+ "getMimeGroups",
+ "getRequestedPermissions",
+ // Tested through asSplit
+ "asSplit",
+ "getSplitNames",
+ "getSplitCodePaths",
+ "getSplitRevisionCodes",
+ "getSplitFlags",
+ "getSplitClassLoaderNames",
+ "getSplitDependencies",
+ "setSplitCodePaths",
+ "setSplitClassLoaderName",
+ "setSplitHasCode",
+ )
+
+ override val baseParams = listOf(
+ AndroidPackage::getAppComponentFactory,
+ AndroidPackage::getAutoRevokePermissions,
+ AndroidPackage::getBackupAgentName,
+ AndroidPackage::getBanner,
+ AndroidPackage::getBaseApkPath,
+ AndroidPackage::getBaseRevisionCode,
+ AndroidPackage::getCategory,
+ AndroidPackage::getClassLoaderName,
+ AndroidPackage::getClassName,
+ AndroidPackage::getCompatibleWidthLimitDp,
+ AndroidPackage::getCompileSdkVersion,
+ AndroidPackage::getCompileSdkVersionCodeName,
+ AndroidPackage::getDataExtractionRules,
+ AndroidPackage::getDescriptionRes,
+ AndroidPackage::getFullBackupContent,
+ AndroidPackage::getGwpAsanMode,
+ AndroidPackage::getIconRes,
+ AndroidPackage::getInstallLocation,
+ AndroidPackage::getLabelRes,
+ AndroidPackage::getLargestWidthLimitDp,
+ AndroidPackage::getLogo,
+ AndroidPackage::getManageSpaceActivityName,
+ AndroidPackage::getMemtagMode,
+ AndroidPackage::getMinSdkVersion,
+ AndroidPackage::getNativeHeapZeroInitialized,
+ AndroidPackage::getNativeLibraryDir,
+ AndroidPackage::getNativeLibraryRootDir,
+ AndroidPackage::getNetworkSecurityConfigRes,
+ AndroidPackage::getNonLocalizedLabel,
+ AndroidPackage::getOverlayCategory,
+ AndroidPackage::getOverlayPriority,
+ AndroidPackage::getOverlayTarget,
+ AndroidPackage::getOverlayTargetName,
+ AndroidPackage::getPackageName,
+ AndroidPackage::getPath,
+ AndroidPackage::getPermission,
+ AndroidPackage::getPrimaryCpuAbi,
+ AndroidPackage::getProcessName,
+ AndroidPackage::getRealPackage,
+ AndroidPackage::getRequiredAccountType,
+ AndroidPackage::getRequiresSmallestWidthDp,
+ AndroidPackage::getResizeableActivity,
+ AndroidPackage::getRestrictedAccountType,
+ AndroidPackage::getRoundIconRes,
+ AndroidPackage::getSeInfo,
+ AndroidPackage::getSeInfoUser,
+ AndroidPackage::getSecondaryCpuAbi,
+ AndroidPackage::getSecondaryNativeLibraryDir,
+ AndroidPackage::getSharedUserId,
+ AndroidPackage::getSharedUserLabel,
+ AndroidPackage::getStaticSharedLibName,
+ AndroidPackage::getStaticSharedLibVersion,
+ AndroidPackage::getTargetSandboxVersion,
+ AndroidPackage::getTargetSdkVersion,
+ AndroidPackage::getTaskAffinity,
+ AndroidPackage::getTheme,
+ AndroidPackage::getUiOptions,
+ AndroidPackage::getUid,
+ AndroidPackage::getVersionName,
+ AndroidPackage::getZygotePreloadName,
+ AndroidPackage::isAllowAudioPlaybackCapture,
+ AndroidPackage::isAllowBackup,
+ AndroidPackage::isAllowClearUserData,
+ AndroidPackage::isAllowClearUserDataOnFailedRestore,
+ AndroidPackage::isAllowNativeHeapPointerTagging,
+ AndroidPackage::isAllowTaskReparenting,
+ AndroidPackage::isBackupInForeground,
+ AndroidPackage::isBaseHardwareAccelerated,
+ AndroidPackage::isCantSaveState,
+ AndroidPackage::isCoreApp,
+ AndroidPackage::isCrossProfile,
+ AndroidPackage::isDebuggable,
+ AndroidPackage::isDefaultToDeviceProtectedStorage,
+ AndroidPackage::isDirectBootAware,
+ AndroidPackage::isEnabled,
+ AndroidPackage::isExternalStorage,
+ AndroidPackage::isExtractNativeLibs,
+ AndroidPackage::isFactoryTest,
+ AndroidPackage::isForceQueryable,
+ AndroidPackage::isFullBackupOnly,
+ AndroidPackage::isGame,
+ AndroidPackage::isHasCode,
+ AndroidPackage::isHasDomainUrls,
+ AndroidPackage::isHasFragileUserData,
+ AndroidPackage::isIsolatedSplitLoading,
+ AndroidPackage::isKillAfterRestore,
+ AndroidPackage::isLargeHeap,
+ AndroidPackage::isMultiArch,
+ AndroidPackage::isNativeLibraryRootRequiresIsa,
+ AndroidPackage::isOdm,
+ AndroidPackage::isOem,
+ AndroidPackage::isOverlay,
+ AndroidPackage::isOverlayIsStatic,
+ AndroidPackage::isPartiallyDirectBootAware,
+ AndroidPackage::isPersistent,
+ AndroidPackage::isPrivileged,
+ AndroidPackage::isProduct,
+ AndroidPackage::isProfileableByShell,
+ AndroidPackage::isRequestLegacyExternalStorage,
+ AndroidPackage::isRequiredForAllUsers,
+ AndroidPackage::isResizeableActivityViaSdkVersion,
+ AndroidPackage::isRestoreAnyVersion,
+ AndroidPackage::isSignedWithPlatformKey,
+ AndroidPackage::isStaticSharedLibrary,
+ AndroidPackage::isStub,
+ AndroidPackage::isSupportsRtl,
+ AndroidPackage::isSystem,
+ AndroidPackage::isSystemExt,
+ AndroidPackage::isTestOnly,
+ AndroidPackage::isUse32BitAbi,
+ AndroidPackage::isUseEmbeddedDex,
+ AndroidPackage::isUsesCleartextTraffic,
+ AndroidPackage::isUsesNonSdkApi,
+ AndroidPackage::isVendor,
+ AndroidPackage::isVisibleToInstantApps,
+ AndroidPackage::isVmSafeMode,
+ AndroidPackage::getMaxAspectRatio,
+ AndroidPackage::getMinAspectRatio,
+ AndroidPackage::hasPreserveLegacyExternalStorage,
+ AndroidPackage::hasRequestForegroundServiceExemption,
+ AndroidPackage::hasRequestRawExternalStorageAccess,
+ )
+
+ override fun extraParams() = listOf(
+ getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"),
+ getter(AndroidPackage::isProfileable, true),
+ getter(AndroidPackage::getVersionCode, 3),
+ getter(AndroidPackage::getVersionCodeMajor, 9),
+ getter(AndroidPackage::getUpgradeKeySets, setOf("testUpgradeKeySet")),
+ getter(AndroidPackage::isAnyDensity, false, 0),
+ getter(AndroidPackage::isResizeable, false, 0),
+ getter(AndroidPackage::isSupportsSmallScreens, false, 0),
+ getter(AndroidPackage::isSupportsNormalScreens, false, 0),
+ getter(AndroidPackage::isSupportsLargeScreens, false, 0),
+ getter(AndroidPackage::isSupportsExtraLargeScreens, false, 0),
+ adder(AndroidPackage::getAdoptPermissions, "test.adopt.PERMISSION"),
+ adder(AndroidPackage::getOriginalPackages, "com.test.original"),
+ adder(AndroidPackage::getImplicitPermissions, "test.implicit.PERMISSION"),
+ adder(AndroidPackage::getLibraryNames, "testLibraryName"),
+ adder(AndroidPackage::getProtectedBroadcasts, "test.protected.BROADCAST"),
+ adder(AndroidPackage::getQueriesPackages, "com.test.package.queries"),
+ adder(AndroidPackage::getQueriesProviders, "com.test.package.queries.provider"),
+ adder(AndroidPackage::getUsesLibraries, "testUsesLibrary"),
+ adder(AndroidPackage::getUsesNativeLibraries, "testUsesNativeLibrary"),
+ adder(AndroidPackage::getUsesOptionalLibraries, "testUsesOptionalLibrary"),
+ adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
+ adder(AndroidPackage::getUsesStaticLibraries, "testUsesStaticLibrary"),
+ getSetByValue(
+ AndroidPackage::getUsesStaticLibrariesVersions,
+ PackageImpl::addUsesStaticLibraryVersion,
+ (testCounter++).toLong(),
+ transformGet = { it?.singleOrNull() }
+ ),
+ getSetByValue(
+ AndroidPackage::areAttributionsUserVisible,
+ ParsingPackage::setAttributionsAreUserVisible,
+ true
+ ),
+ getSetByValue2(
+ AndroidPackage::getOverlayables,
+ PackageImpl::addOverlayable,
+ "testOverlayableName" to "testActorName",
+ transformGet = { "testOverlayableName" to it["testOverlayableName"] }
+ ),
+ getSetByValue(
+ AndroidPackage::getMetaData,
+ PackageImpl::setMetaData,
+ "testBundleKey" to "testBundleValue",
+ transformGet = { "testBundleKey" to it?.getString("testBundleKey") },
+ transformSet = { Bundle().apply { putString(it.first, it.second) } }
+ ),
+ getSetByValue(
+ AndroidPackage::getAttributions,
+ PackageImpl::addAttribution,
+ Triple("testTag", 13, listOf("testInherit")),
+ transformGet = { it.singleOrNull()?.let { Triple(it.tag, it.label, it.inheritFrom) } },
+ transformSet = { it?.let { ParsedAttribution(it.first, it.second, it.third) } }
+ ),
+ getSetByValue2(
+ AndroidPackage::getKeySetMapping,
+ PackageImpl::addKeySet,
+ "testKeySetName" to testKey(),
+ transformGet = { "testKeySetName" to it["testKeySetName"]?.singleOrNull() },
+ ),
+ getSetByValue(
+ AndroidPackage::getPermissionGroups,
+ PackageImpl::addPermissionGroup,
+ "test.permission.GROUP",
+ transformGet = { it.singleOrNull()?.name },
+ transformSet = { ParsedPermissionGroup().apply { setName(it) } }
+ ),
+ getSetByValue2(
+ AndroidPackage::getPreferredActivityFilters,
+ PackageImpl::addPreferredActivityFilter,
+ "TestClassName" to ParsedIntentInfo().apply {
+ addDataScheme("http")
+ addDataAuthority("test.pm.server.android.com", null)
+ },
+ transformGet = { it.singleOrNull()?.let { it.first to it.second } },
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ { it.first },
+ { it.second.schemesIterator().asSequence().singleOrNull() },
+ { it.second.authoritiesIterator().asSequence().singleOrNull()?.host },
+ )
+ }
+ ),
+ getSetByValue(
+ AndroidPackage::getQueriesIntents,
+ PackageImpl::addQueriesIntent,
+ Intent(Intent.ACTION_VIEW, Uri.parse("https://test.pm.server.android.com")),
+ transformGet = { it.singleOrNull() },
+ compare = { first, second -> first?.filterEquals(second) },
+ ),
+ getSetByValue(
+ AndroidPackage::getRestrictUpdateHash,
+ PackageImpl::setRestrictUpdateHash,
+ byteArrayOf(0, 1, 2, 3, 4),
+ compare = ByteArray::contentEquals
+ ),
+ getSetByValue(
+ AndroidPackage::getSigningDetails,
+ PackageImpl::setSigningDetails,
+ testKey(),
+ transformGet = { it.publicKeys?.takeIf { it.size > 0 }?.valueAt(0) },
+ transformSet = {
+ SigningDetails(
+ null,
+ SigningDetails.SignatureSchemeVersion.UNKNOWN,
+ ArraySet<PublicKey>().apply { add(it) },
+ null
+ )
+ }
+ ),
+ getSetByValue(
+ AndroidPackage::getUsesStaticLibrariesCertDigests,
+ PackageImpl::addUsesStaticLibraryCertDigests,
+ arrayOf("testCertDigest"),
+ transformGet = { it?.singleOrNull() },
+ compare = Array<String?>?::contentEquals
+ ),
+ getSetByValue(
+ AndroidPackage::getActivities,
+ PackageImpl::addActivity,
+ "TestActivityName",
+ transformGet = { it.singleOrNull()?.name.orEmpty() },
+ transformSet = { ParsedActivity().apply { name = it }.withMimeGroups() }
+ ),
+ getSetByValue(
+ AndroidPackage::getReceivers,
+ PackageImpl::addReceiver,
+ "TestReceiverName",
+ transformGet = { it.singleOrNull()?.name.orEmpty() },
+ transformSet = { ParsedActivity().apply { name = it }.withMimeGroups() }
+ ),
+ getSetByValue(
+ AndroidPackage::getServices,
+ PackageImpl::addService,
+ "TestServiceName",
+ transformGet = { it.singleOrNull()?.name.orEmpty() },
+ transformSet = { ParsedService().apply { name = it }.withMimeGroups() }
+ ),
+ getSetByValue(
+ AndroidPackage::getProviders,
+ PackageImpl::addProvider,
+ "TestProviderName",
+ transformGet = { it.singleOrNull()?.name.orEmpty() },
+ transformSet = { ParsedProvider().apply { name = it }.withMimeGroups() }
+ ),
+ getSetByValue(
+ AndroidPackage::getInstrumentations,
+ PackageImpl::addInstrumentation,
+ "TestInstrumentationName",
+ transformGet = { it.singleOrNull()?.name.orEmpty() },
+ transformSet = { ParsedInstrumentation().apply { name = it } }
+ ),
+ getSetByValue(
+ AndroidPackage::getConfigPreferences,
+ PackageImpl::addConfigPreference,
+ testCounter++,
+ transformGet = { it.singleOrNull()?.reqGlEsVersion ?: -1 },
+ transformSet = { ConfigurationInfo().apply { reqGlEsVersion = it } }
+ ),
+ getSetByValue(
+ AndroidPackage::getFeatureGroups,
+ PackageImpl::addFeatureGroup,
+ "test.feature.GROUP",
+ transformGet = { it.singleOrNull()?.features?.singleOrNull()?.name.orEmpty() },
+ transformSet = {
+ FeatureGroupInfo().apply {
+ features = arrayOf(FeatureInfo().apply { name = it })
+ }
+ }
+ ),
+ getSetByValue(
+ AndroidPackage::getPermissions,
+ PackageImpl::addPermission,
+ "test.PERMISSION",
+ transformGet = { it.singleOrNull()?.name.orEmpty() },
+ transformSet = { ParsedPermission().apply { name = it } }
+ ),
+ getSetByValue(
+ AndroidPackage::getUsesPermissions,
+ PackageImpl::addUsesPermission,
+ "test.USES_PERMISSION",
+ transformGet = {
+ // Need to strip implicit permission, which calls addUsesPermission when added
+ it.filterNot { it.name == "test.implicit.PERMISSION" }
+ .singleOrNull()?.name.orEmpty()
+ },
+ transformSet = { ParsedUsesPermission(it, 0) }
+ ),
+ getSetByValue(
+ AndroidPackage::getReqFeatures,
+ PackageImpl::addReqFeature,
+ "test.feature.INFO",
+ transformGet = { it.singleOrNull()?.name.orEmpty() },
+ transformSet = { FeatureInfo().apply { name = it } }
+ ),
+ getSetByValue(
+ AndroidPackage::getMinExtensionVersions,
+ PackageImpl::setMinExtensionVersions,
+ SparseIntArray().apply { put(testCounter++, testCounter++) },
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ { it.size() },
+ { it.keyAt(0) },
+ { it.valueAt(0) },
+ )
+ }
+ ),
+ getSetByValue(
+ AndroidPackage::getProcesses,
+ PackageImpl::setProcesses,
+ mapOf("testProcess" to ParsedProcess().apply { name = "testProcessName" }),
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ { it["testProcess"]?.name },
+ )
+ }
+ ),
+ getSetByValue(
+ AndroidPackage::getProperties,
+ PackageImpl::addProperty,
+ PackageManager.Property(
+ "testPropertyName",
+ "testPropertyValue",
+ "testPropertyClassName",
+ "testPropertyPackageName"
+ ),
+ transformGet = { it["testPropertyName"] },
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ PackageManager.Property::getName,
+ PackageManager.Property::getClassName,
+ PackageManager.Property::getPackageName,
+ PackageManager.Property::getString,
+ )
+ }
+ ),
+ )
+
+ override fun initialObject() = PackageImpl.forParsing(
+ "com.example.test",
+ "/test/test/base.apk",
+ "/test/test",
+ mockThrowOnUnmocked {
+ whenever(getInteger(R.styleable.AndroidManifest_revisionCode, 0)) { 4 }
+ whenever(getBoolean(R.styleable.AndroidManifest_isolatedSplits, false)) { true }
+
+ // Return invalid values here so that the getter/setter is tested properly
+ whenever(getInteger(R.styleable.AndroidManifest_versionCode, 0)) { -1 }
+ whenever(getInteger(R.styleable.AndroidManifest_versionCodeMajor, 0)) { -1 }
+ whenever(
+ getNonConfigurationString(
+ R.styleable.AndroidManifest_versionName,
+ 0
+ )
+ ) { "" }
+ whenever(getInteger(R.styleable.AndroidManifest_compileSdkVersion, 0)) { 31 }
+ whenever(
+ getNonConfigurationString(
+ R.styleable.AndroidManifest_compileSdkVersionCodename,
+ 0
+ )
+ ) { "" }
+ },
+ true
+ )
+ .asSplit(
+ arrayOf("testSplitNameZero", "testSplitNameOne"),
+ arrayOf("/test/testSplitZero.apk", "/test/testSplitOne.apk"),
+ intArrayOf(10, 11),
+ SparseArray<IntArray>().apply {
+ put(0, intArrayOf(-1))
+ put(1, intArrayOf(0))
+ }
+ )
+ .setSplitHasCode(0, true)
+ .setSplitHasCode(1, false)
+ .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
+ .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
+
+ override fun extraAssertions(before: Parcelable, after: Parcelable) {
+ super.extraAssertions(before, after)
+ after as PackageImpl
+ expect.that(after.manifestPackageName).isEqualTo("com.example.test")
+ expect.that(after.isCoreApp).isTrue()
+ expect.that(after.isIsolatedSplitLoading).isEqualTo(true)
+ expect.that(after.longVersionCode).isEqualTo(38654705667)
+ expect.that(after.requestedPermissions)
+ .containsExactlyElementsIn(after.usesPermissions.map { it.name })
+ .inOrder()
+
+ expect.that(after.mimeGroups).containsExactly(
+ "TestActivityName/mimeGroup",
+ "TestReceiverName/mimeGroup",
+ "TestServiceName/mimeGroup",
+ "TestProviderName/mimeGroup"
+ )
+
+ expect.that(after.splitNames).asList()
+ .containsExactly("testSplitNameZero", "testSplitNameOne")
+ .inOrder()
+ expect.that(after.splitCodePaths).asList()
+ .containsExactly("/test/testSplitZero.apk", "/test/testSplitOne.apk")
+ .inOrder()
+ expect.that(after.splitRevisionCodes).asList()
+ .containsExactly(10, 11)
+ .inOrder()
+ expect.that(after.splitFlags).asList()
+ .containsExactly(ApplicationInfo.FLAG_HAS_CODE, 0)
+ .inOrder()
+ expect.that(after.splitClassLoaderNames).asList()
+ .containsExactly("testSplitClassLoaderNameZero", "testSplitClassLoaderNameOne")
+ .inOrder()
+
+ expect.that(after.splitDependencies).isNotNull()
+ after.splitDependencies?.let {
+ expect.that(it.size()).isEqualTo(2)
+ expect.that(it.get(0)).asList().containsExactly(-1)
+ expect.that(it.get(1)).asList().containsExactly(0)
+ }
+ }
+
+ private fun testKey() = KeyPairGenerator.getInstance("RSA")
+ .generateKeyPair()
+ .public
+
+ private fun <T : ParsedComponent> T.withMimeGroups() = apply {
+ val componentName = name
+ addIntent(ParsedIntentInfo().apply {
+ addMimeGroup("$componentName/mimeGroup")
+ })
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
new file mode 100644
index 0000000..e16a187
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.server.pm.test.util.IgnoreableExpect
+import com.google.common.truth.Expect
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import java.util.Objects
+import kotlin.contracts.ExperimentalContracts
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.reflect.KFunction1
+import kotlin.reflect.KFunction2
+import kotlin.reflect.KFunction3
+import kotlin.reflect.KVisibility
+import kotlin.reflect.full.allSuperclasses
+import kotlin.reflect.full.createInstance
+import kotlin.reflect.full.isSubclassOf
+import kotlin.reflect.full.memberFunctions
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.staticProperties
+import kotlin.reflect.jvm.jvmErasure
+
+
+@ExperimentalContracts
+abstract class ParcelableComponentTest(
+ private val getterType: KClass<*>,
+ private val setterType: KClass<out Parcelable>
+) {
+
+ companion object {
+ private val DEFAULT_EXCLUDED = listOf(
+ // Java
+ "toString",
+ "equals",
+ "hashCode",
+ // Parcelable
+ "getStability",
+ "describeContents",
+ "writeToParcel",
+ // @DataClass
+ "__metadata"
+ )
+ }
+
+ internal val ignoreableExpect = IgnoreableExpect()
+
+ // Hides internal type
+ @get:Rule
+ val ignoreableAsTestRule: TestRule = ignoreableExpect
+
+ val expect: Expect
+ get() = ignoreableExpect.expect
+
+ protected var testCounter = 1
+
+ protected abstract val defaultImpl: Any
+ protected abstract val creator: Parcelable.Creator<out Parcelable>
+
+ protected open val excludedMethods: Collection<String> = emptyList()
+
+ protected abstract val baseParams: Collection<KFunction1<*, Any?>>
+
+ private val getters = getterType.memberFunctions
+ .filterNot { DEFAULT_EXCLUDED.contains(it.name) }
+
+ private val setters = setterType.memberFunctions
+ .filterNot { DEFAULT_EXCLUDED.contains(it.name) }
+
+ constructor(kClass: KClass<out Parcelable>) : this(kClass, kClass)
+
+ @Before
+ fun checkNoPublicFields() {
+ // Fields are not currently testable, and the idea is to enforce interface access for
+ // immutability purposes, so disallow any public fields from existing.
+ expect.that(getterType.memberProperties.filter { it.visibility == KVisibility.PUBLIC }
+ .filterNot { DEFAULT_EXCLUDED.contains(it.name) })
+ .isEmpty()
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <ObjectType, ReturnType> buildParams(
+ getFunction: KFunction1<ObjectType, ReturnType>,
+ ): Param? {
+ return buildParams<ObjectType, ReturnType, ReturnType, ReturnType>(
+ getFunction,
+ autoValue(getFunction) as ReturnType ?: return null
+ )
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <ObjectType, ReturnType, SetType : Any?, CompareType : Any?> buildParams(
+ getFunction: KFunction1<ObjectType, ReturnType>,
+ value: SetType,
+ ): Param? {
+ return getSetByValue<ObjectType, ReturnType, SetType, Any?>(
+ getFunction,
+ findSetFunction(getFunction) ?: return null,
+ value
+ )
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <ObjectType, ReturnType> findSetFunction(
+ getFunction: KFunction1<ObjectType, ReturnType>
+ ): KFunction2<ObjectType, ReturnType, Any?>? {
+ val getFunctionName = getFunction.name
+ val prefix = when {
+ getFunctionName.startsWith("get") -> "get"
+ getFunctionName.startsWith("is") -> "is"
+ getFunctionName.startsWith("has") -> "has"
+ else -> throw IllegalArgumentException("Unsupported method name $getFunctionName")
+ }
+ val setFunctionName = "set" + getFunctionName.removePrefix(prefix)
+ val setFunction = setters.filter { it.name == setFunctionName }
+ .minByOrNull { it.parameters.size }
+
+ if (setFunction == null) {
+ expect.withMessage("$getFunctionName does not have corresponding $setFunctionName")
+ .fail()
+ return null
+ }
+
+ return setFunction as KFunction2<ObjectType, ReturnType, Any?>
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <ObjectType, ReturnType, SetType> findAddFunction(
+ getFunction: KFunction1<ObjectType, ReturnType>
+ ): KFunction2<ObjectType, SetType, Any?>? {
+ val getFunctionName = getFunction.name
+ if (!getFunctionName.startsWith("get")) {
+ throw IllegalArgumentException("Unsupported method name $getFunctionName")
+ }
+
+ val setFunctionName = "add" + getFunctionName.removePrefix("get").run {
+ // Remove plurality
+ when {
+ endsWith("ies") -> "${removeSuffix("ies")}y"
+ endsWith("s") -> removeSuffix("s")
+ else -> this
+ }
+ }
+
+ val setFunction = setters.filter { it.name == setFunctionName }
+ .minByOrNull { it.parameters.size }
+
+ if (setFunction == null) {
+ expect.withMessage("$getFunctionName does not have corresponding $setFunctionName")
+ .fail()
+ return null
+ }
+
+ return setFunction as KFunction2<ObjectType, SetType, Any?>
+ }
+
+ protected fun <ObjectType, ReturnType> getter(
+ getFunction: KFunction1<ObjectType, ReturnType>,
+ valueToSet: ReturnType
+ ) = buildParams<ObjectType, ReturnType, ReturnType, ReturnType>(getFunction, valueToSet)
+
+ protected fun <ObjectType, ReturnType, SetType : Any?, CompareType : Any?> getter(
+ getFunction: KFunction1<ObjectType, ReturnType>,
+ expectedValue: CompareType,
+ valueToSet: SetType
+ ): Param? {
+ return getSetByValue(
+ getFunction,
+ findSetFunction(getFunction) ?: return null,
+ value = expectedValue,
+ transformSet = { valueToSet }
+ )
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ protected fun <ObjectType, ReturnType> adder(
+ getFunction: KFunction1<ObjectType, ReturnType>,
+ value: ReturnType,
+ ): Param? {
+ return getSetByValue(
+ getFunction,
+ findAddFunction<ObjectType, Any?, ReturnType>(getFunction) ?: return null,
+ value,
+ transformGet = {
+ // Primitive arrays don't implement Iterable, so cast manually
+ when (it) {
+ is BooleanArray -> it.singleOrNull()
+ is IntArray -> it.singleOrNull()
+ is LongArray -> it.singleOrNull()
+ is Iterable<*> -> it.singleOrNull()
+ else -> null
+ }
+ },
+ )
+ }
+
+ /**
+ * Method to provide custom getter and setter logic for values which are not simple primitives
+ * or cannot be directly compared using [Objects.equals].
+ *
+ * @param getFunction the getter function which will be called and marked as tested
+ * @param setFunction the setter function which will be called and marked as tested
+ * @param value the value for comparison through the parcel-unparcel cycle, which can be
+ * anything, like the [String] ID of an inner object
+ * @param transformGet the function to transform the result of [getFunction] into [value]
+ * @param transformSet the function to transform [value] into an input for [setFunction]
+ * @param compare the function that compares the pre/post-parcel [value] objects
+ */
+ @Suppress("UNCHECKED_CAST")
+ protected fun <ObjectType, ReturnType, SetType : Any?, CompareType : Any?> getSetByValue(
+ getFunction: KFunction1<ObjectType, ReturnType>,
+ setFunction: KFunction2<ObjectType, SetType, Any?>,
+ value: CompareType,
+ transformGet: (ReturnType) -> CompareType = { it as CompareType },
+ transformSet: (CompareType) -> SetType = { it as SetType },
+ compare: (CompareType, CompareType) -> Boolean? = Objects::equals
+ ) = Param(
+ getFunction.name,
+ { transformGet(getFunction.call(it as ObjectType)) },
+ setFunction.name,
+ { setFunction.call(it.first() as ObjectType, transformSet(it[1] as CompareType)) },
+ { value },
+ { first, second -> compare(first as CompareType, second as CompareType) == true }
+ )
+
+ /**
+ * Variant of [getSetByValue] that allows specifying a [setFunction] with 2 inputs.
+ */
+ @Suppress("UNCHECKED_CAST")
+ protected fun <ObjectType, ReturnType, SetType1 : Any?, SetType2 : Any?, CompareType : Any?>
+ getSetByValue2(
+ getFunction: KFunction1<ObjectType, ReturnType>,
+ setFunction: KFunction3<ObjectType, SetType1, SetType2, Any>,
+ value: CompareType,
+ transformGet: (ReturnType) -> CompareType = { it as CompareType },
+ transformSet: (CompareType) -> Pair<SetType1, SetType2> =
+ { it as Pair<SetType1, SetType2> },
+ compare: (CompareType, CompareType) -> Boolean = Objects::equals
+ ) = Param(
+ getFunction.name,
+ { transformGet(getFunction.call(it as ObjectType)) },
+ setFunction.name,
+ {
+ val pair = transformSet(it[1] as CompareType)
+ setFunction.call(it.first() as ObjectType, pair.first, pair.second)
+ },
+ { value },
+ { first, second -> compare(first as CompareType, second as CompareType) }
+ )
+
+ protected fun autoValue(getFunction: KFunction<*>) = when (getFunction.returnType.jvmErasure) {
+ Boolean::class -> (getFunction.call(defaultImpl) as Boolean?)?.not() ?: true
+ CharSequence::class,
+ String::class -> getFunction.name + "TEST"
+ Int::class -> testCounter++
+ Long::class -> (testCounter++).toLong()
+ Float::class -> (testCounter++).toFloat()
+ else -> {
+ expect.withMessage("${getFunction.name} needs to provide value").fail()
+ null
+ }
+ }
+
+ /**
+ * Verifies two instances are equivalent via a series of properties. For use when a public API
+ * class has not implemented equals.
+ */
+ @Suppress("UNCHECKED_CAST")
+ protected fun <T : Any> equalBy(
+ first: T?,
+ second: T?,
+ vararg properties: (T) -> Any?
+ ) = properties.all { property ->
+ first?.let { property(it) } == second?.let { property(it) }
+ }
+
+ @Test
+ fun valueComparison() {
+ val params = baseParams.mapNotNull(::buildParams) + extraParams().filterNotNull()
+ val before = initialObject()
+
+ params.forEach { it.setFunction(arrayOf(before, it.value())) }
+
+ val parcel = Parcel.obtain()
+ writeToParcel(parcel, before)
+
+ val dataSize = parcel.dataSize()
+
+ parcel.setDataPosition(0)
+
+ val after = creator.createFromParcel(parcel)
+
+ expect.withMessage("Mismatched write and read data sizes")
+ .that(parcel.dataPosition())
+ .isEqualTo(dataSize)
+
+ parcel.recycle()
+
+ runAssertions(params, before, after)
+ }
+
+ @Test
+ open fun parcellingSize() {
+ val parcelOne = Parcel.obtain()
+ writeToParcel(parcelOne, initialObject())
+
+ val parcelTwo = Parcel.obtain()
+ initialObject().writeToParcel(parcelTwo, 0)
+
+ val superDataSizes = setterType.allSuperclasses
+ .filter { it.isSubclassOf(Parcelable::class) }
+ .mapNotNull { it.memberFunctions.find { it.name == "writeToParcel" } }
+ .filter { it.isFinal }
+ .map {
+ val parcel = Parcel.obtain()
+ initialObject().writeToParcel(parcel, 0)
+ parcel.dataSize().also { parcel.recycle() }
+ }
+
+ if ((superDataSizes + parcelOne.dataSize() + parcelTwo.dataSize()).distinct().size != 1) {
+ listOf(getterType, setterType).distinct().forEach {
+ val creatorProperties = it.staticProperties.filter { it.name == "CREATOR" }
+ if (creatorProperties.size > 1) {
+ expect.withMessage(
+ "Multiple matching CREATOR fields found for" +
+ it.qualifiedName
+ )
+ .that(creatorProperties)
+ .hasSize(1)
+ } else {
+ val creator = creatorProperties.single().get()
+ if (creator !is Parcelable.Creator<*>) {
+ expect.that(creator).isInstanceOf(Parcelable.Creator::class.java)
+ return
+ }
+
+ parcelTwo.setDataPosition(0)
+ val parcelable = creator.createFromParcel(parcelTwo)
+ if (parcelable::class.isSubclassOf(setterType)) {
+ expect.withMessage(
+ "${it.qualifiedName} which does not safely override writeToParcel " +
+ "cannot contain a subclass CREATOR field"
+ )
+ .fail()
+ }
+ }
+ }
+ }
+
+ parcelOne.recycle()
+ parcelTwo.recycle()
+ }
+
+ private fun runAssertions(params: List<Param>, before: Parcelable, after: Parcelable) {
+ params.forEach {
+ val actual = it.getFunction(after)
+ val expected = it.value()
+ val equal = it.compare(actual, expected)
+ expect.withMessage("${it.getFunctionName} was $actual, expected $expected")
+ .that(equal)
+ .isTrue()
+ }
+
+ extraAssertions(before, after)
+
+ // TODO: Handle method overloads?
+ val expectedFunctions = (getters.map { it.name }
+ + setters.map { it.name }
+ - excludedMethods)
+ .distinct()
+
+ val allTestedFunctions = params.flatMap {
+ listOfNotNull(it.getFunctionName, it.setFunctionName)
+ }
+ expect.that(allTestedFunctions).containsExactlyElementsIn(expectedFunctions)
+ }
+
+ open fun extraParams(): Collection<Param?> = emptyList()
+
+ open fun initialObject(): Parcelable = setterType.createInstance()
+
+ open fun extraAssertions(before: Parcelable, after: Parcelable) {}
+
+ open fun writeToParcel(parcel: Parcel, value: Parcelable) = value.writeToParcel(parcel, 0)
+
+ data class Param(
+ val getFunctionName: String,
+ val getFunction: (Any?) -> Any?,
+ val setFunctionName: String?,
+ val setFunction: (Array<Any?>) -> Unit,
+ val value: () -> Any?,
+ val compare: (Any?, Any?) -> Boolean = Objects::equals
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorInvalidTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorInvalidTest.kt
new file mode 100644
index 0000000..d506190
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorInvalidTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.server.pm.test.parsing.parcelling.java.TestSubWithCreator
+import com.android.server.pm.test.parsing.parcelling.java.TestSuperClass
+import org.junit.Test
+import kotlin.contracts.ExperimentalContracts
+
+/**
+ * Verifies the failing side of [ParcelableCreatorValidTest]. The sole difference is the addition
+ * of [TestSubWithCreator.CREATOR].
+ */
+@ExperimentalContracts
+class ParcelableCreatorInvalidTest :
+ ParcelableComponentTest(TestSuperClass::class, TestSubWithCreator::class) {
+
+ override val defaultImpl = TestSubWithCreator()
+
+ override val creator = object : Parcelable.Creator<Parcelable> {
+ override fun createFromParcel(source: Parcel) = TestSubWithCreator(source)
+ override fun newArray(size: Int) = Array<TestSubWithCreator?>(size) { null }
+ }
+
+ override val excludedMethods = listOf("writeSubToParcel")
+
+ override val baseParams = listOf(TestSuperClass::getSuperString)
+
+ override fun writeToParcel(parcel: Parcel, value: Parcelable) {
+ (value as TestSubWithCreator).writeSubToParcel(parcel, 0)
+ }
+
+ @Test
+ override fun parcellingSize() {
+ super.parcellingSize()
+ if (expect.hasFailures()) {
+ // This is a hack to ignore an expected failure result. Doing it this way, rather than
+ // adding a switch in the test itself, prevents it from accidentally passing through a
+ // programming error.
+ ignoreableExpect.ignore()
+ }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorValidTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorValidTest.kt
new file mode 100644
index 0000000..f1bc7b5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorValidTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.server.pm.test.parsing.parcelling.java.TestSubWithoutCreator
+import com.android.server.pm.test.parsing.parcelling.java.TestSuperClass
+import kotlin.contracts.ExperimentalContracts
+
+/**
+ * Tests the [Parcelable] CREATOR verification by using a mock object with known differences to
+ * ensure that the method succeeds/fails.
+ */
+@ExperimentalContracts
+class ParcelableCreatorValidTest :
+ ParcelableComponentTest(TestSuperClass::class, TestSubWithoutCreator::class) {
+
+ override val defaultImpl = TestSubWithoutCreator()
+
+ override val creator = object : Parcelable.Creator<Parcelable> {
+ override fun createFromParcel(source: Parcel) = TestSubWithoutCreator(source)
+ override fun newArray(size: Int) = Array<TestSubWithoutCreator?>(size) { null }
+ }
+
+ override val excludedMethods = listOf("writeSubToParcel")
+
+ override val baseParams = listOf(TestSuperClass::getSuperString)
+
+ override fun writeToParcel(parcel: Parcel, value: Parcelable) {
+ (value as TestSubWithoutCreator).writeSubToParcel(parcel, 0)
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
new file mode 100644
index 0000000..ece600b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.ActivityInfo
+import android.content.pm.parsing.component.ParsedActivity
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedActivityTest : ParsedMainComponentTest(ParsedActivity::class) {
+
+ override val defaultImpl = ParsedActivity()
+ override val creator = ParsedActivity.CREATOR
+
+ override val mainComponentSubclassBaseParams = listOf(
+ ParsedActivity::getPermission,
+ ParsedActivity::getColorMode,
+ ParsedActivity::getConfigChanges,
+ ParsedActivity::getDocumentLaunchMode,
+ ParsedActivity::getLaunchMode,
+ ParsedActivity::getLockTaskLaunchMode,
+ ParsedActivity::getMaxAspectRatio,
+ ParsedActivity::getMaxRecents,
+ ParsedActivity::getMinAspectRatio,
+ ParsedActivity::getParentActivityName,
+ ParsedActivity::getPersistableMode,
+ ParsedActivity::getPrivateFlags,
+ ParsedActivity::getRequestedVrComponent,
+ ParsedActivity::getResizeMode,
+ ParsedActivity::getRotationAnimation,
+ ParsedActivity::getScreenOrientation,
+ ParsedActivity::getSoftInputMode,
+ ParsedActivity::getTargetActivity,
+ ParsedActivity::getTaskAffinity,
+ ParsedActivity::getTheme,
+ ParsedActivity::getUiOptions,
+ ParsedActivity::isSupportsSizeChanges,
+ )
+
+ override fun mainComponentSubclassExtraParams() = listOf(
+ getSetByValue(
+ ParsedActivity::getWindowLayout,
+ ParsedActivity::setWindowLayout,
+ ActivityInfo.WindowLayout(1, 1f, 2, 1f, 3, 4, 5),
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ ActivityInfo.WindowLayout::width,
+ ActivityInfo.WindowLayout::widthFraction,
+ ActivityInfo.WindowLayout::height,
+ ActivityInfo.WindowLayout::heightFraction,
+ ActivityInfo.WindowLayout::gravity,
+ ActivityInfo.WindowLayout::minWidth,
+ ActivityInfo.WindowLayout::minHeight
+ )
+ }
+ )
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
new file mode 100644
index 0000000..e739dc7
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedAttribution
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedAttributionTest : ParcelableComponentTest(ParsedAttribution::class) {
+
+ override val defaultImpl = ParsedAttribution("", 0, emptyList())
+ override val creator = ParsedAttribution.CREATOR
+
+ override val baseParams = listOf(
+ ParsedAttribution::getTag,
+ ParsedAttribution::getLabel,
+ )
+
+ override fun extraParams() = listOf(
+ getter(ParsedAttribution::getInheritFrom, listOf("testInheritFrom"))
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
new file mode 100644
index 0000000..0a22f6d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.PackageManager
+import android.content.pm.parsing.component.ParsedComponent
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Bundle
+import android.os.Parcelable
+import kotlin.contracts.ExperimentalContracts
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction1
+
+@ExperimentalContracts
+abstract class ParsedComponentTest(kClass: KClass<out Parcelable>) :
+ ParcelableComponentTest(kClass) {
+
+ final override val excludedMethods
+ get() = subclassExcludedMethods + listOf(
+ // Method aliases/utilities
+ "getClassName",
+ "getComponentName",
+ "setProperties" // Tested though addProperty
+ )
+
+ open val subclassExcludedMethods: Collection<String> = emptyList()
+
+ final override val baseParams
+ get() = subclassBaseParams + listOf(
+ ParsedComponent::getBanner,
+ ParsedComponent::getDescriptionRes,
+ ParsedComponent::getFlags,
+ ParsedComponent::getIcon,
+ ParsedComponent::getLabelRes,
+ ParsedComponent::getLogo,
+ ParsedComponent::getName,
+ ParsedComponent::getNonLocalizedLabel,
+ ParsedComponent::getPackageName,
+ )
+
+ abstract val subclassBaseParams: Collection<KFunction1<*, Any?>>
+
+ final override fun extraParams() = subclassExtraParams() + listOf(
+ getSetByValue(
+ ParsedComponent::getIntents,
+ ParsedComponent::addIntent,
+ "TestLabel",
+ transformGet = { it.singleOrNull()?.nonLocalizedLabel },
+ transformSet = { ParsedIntentInfo().setNonLocalizedLabel(it) },
+ ),
+ getSetByValue(
+ ParsedComponent::getProperties,
+ ParsedComponent::addProperty,
+ PackageManager.Property(
+ "testPropertyName",
+ "testPropertyValue",
+ "testPropertyClassName",
+ "testPropertyPackageName"
+ ),
+ transformGet = { it["testPropertyName"] },
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ PackageManager.Property::getName,
+ PackageManager.Property::getClassName,
+ PackageManager.Property::getPackageName,
+ PackageManager.Property::getString,
+ )
+ }
+ ),
+ getSetByValue(
+ ParsedComponent::getMetaData,
+ ParsedComponent::setMetaData,
+ "testBundleKey" to "testBundleValue",
+ transformGet = { "testBundleKey" to it?.getString("testBundleKey") },
+ transformSet = { Bundle().apply { putString(it.first, it.second) } }
+ ),
+ )
+
+ open fun subclassExtraParams(): Collection<Param?> = emptyList()
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
new file mode 100644
index 0000000..b7a85cc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedInstrumentation
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedInstrumentationTest : ParsedComponentTest(ParsedInstrumentation::class) {
+
+ override val defaultImpl = ParsedInstrumentation()
+ override val creator = ParsedInstrumentation.CREATOR
+
+ override val subclassBaseParams = listOf(
+ ParsedInstrumentation::getTargetPackage,
+ ParsedInstrumentation::getTargetProcesses,
+ ParsedInstrumentation::isFunctionalTest,
+ ParsedInstrumentation::isHandleProfiling,
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
new file mode 100644
index 0000000..e27bdf2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.PatternMatcher
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedIntentInfoTest : ParcelableComponentTest(ParsedIntentInfo::class) {
+
+ override val defaultImpl = ParsedIntentInfo()
+
+ override val creator = object : Parcelable.Creator<ParsedIntentInfo> {
+ override fun createFromParcel(source: Parcel) = ParsedIntentInfo(source)
+ override fun newArray(size: Int) = Array<ParsedIntentInfo?>(size) { null }
+ }
+
+ override val excludedMethods = listOf(
+ // Used to parcel
+ "writeIntentInfoToParcel",
+ // All remaining IntentFilter methods, which are out of scope
+ "hasDataPath",
+ "hasDataSchemeSpecificPart",
+ "matchAction",
+ "matchData",
+ "actionsIterator",
+ "addAction",
+ "addCategory",
+ "addDataAuthority",
+ "addDataPath",
+ "addDataScheme",
+ "addDataSchemeSpecificPart",
+ "addDataType",
+ "addDynamicDataType",
+ "addMimeGroup",
+ "asPredicate",
+ "asPredicateWithTypeResolution",
+ "authoritiesIterator",
+ "categoriesIterator",
+ "clearDynamicDataTypes",
+ "countActions",
+ "countCategories",
+ "countDataAuthorities",
+ "countDataPaths",
+ "countDataSchemeSpecificParts",
+ "countDataSchemes",
+ "countDataTypes",
+ "countMimeGroups",
+ "countStaticDataTypes",
+ "dataTypes",
+ "debugCheck",
+ "dump",
+ "dumpDebug",
+ "getAction",
+ "getAutoVerify",
+ "getCategory",
+ "getDataAuthority",
+ "getDataPath",
+ "getDataScheme",
+ "getDataSchemeSpecificPart",
+ "getDataType",
+ "getHosts",
+ "getHostsList",
+ "getMimeGroup",
+ "getOrder",
+ "getPriority",
+ "getVisibilityToInstantApp",
+ "handleAllWebDataURI",
+ "handlesWebUris",
+ "hasAction",
+ "hasCategory",
+ "hasDataAuthority",
+ "hasDataScheme",
+ "hasDataType",
+ "hasExactDataType",
+ "hasExactDynamicDataType",
+ "hasExactStaticDataType",
+ "hasMimeGroup",
+ "isExplicitlyVisibleToInstantApp",
+ "isImplicitlyVisibleToInstantApp",
+ "isVerified",
+ "isVisibleToInstantApp",
+ "match",
+ "matchCategories",
+ "matchDataAuthority",
+ "mimeGroupsIterator",
+ "needsVerification",
+ "pathsIterator",
+ "readFromXml",
+ "schemeSpecificPartsIterator",
+ "schemesIterator",
+ "setAutoVerify",
+ "setOrder",
+ "setPriority",
+ "setVerified",
+ "setVisibilityToInstantApp",
+ "typesIterator",
+ "writeToXml",
+ )
+
+ override val baseParams = listOf(
+ ParsedIntentInfo::getIcon,
+ ParsedIntentInfo::getLabelRes,
+ ParsedIntentInfo::isHasDefault,
+ ParsedIntentInfo::getNonLocalizedLabel,
+ )
+
+ override fun initialObject() = ParsedIntentInfo().apply {
+ addAction("test.ACTION")
+ addDataAuthority("testAuthority", "404")
+ addCategory("test.CATEGORY")
+ addMimeGroup("testMime")
+ addDataPath("testPath", PatternMatcher.PATTERN_LITERAL)
+ }
+
+ override fun extraAssertions(before: Parcelable, after: Parcelable) {
+ super.extraAssertions(before, after)
+ after as ParsedIntentInfo
+ expect.that(after.actionsIterator().asSequence().singleOrNull())
+ .isEqualTo("test.ACTION")
+
+ val authority = after.authoritiesIterator().asSequence().singleOrNull()
+ expect.that(authority?.host).isEqualTo("testAuthority")
+ expect.that(authority?.port).isEqualTo(404)
+
+ expect.that(after.categoriesIterator().asSequence().singleOrNull())
+ .isEqualTo("test.CATEGORY")
+ expect.that(after.mimeGroupsIterator().asSequence().singleOrNull())
+ .isEqualTo("testMime")
+ expect.that(after.hasDataPath("testPath")).isTrue()
+ }
+
+ override fun writeToParcel(parcel: Parcel, value: Parcelable) =
+ ParsedIntentInfo.PARCELER.parcel(value as ParsedIntentInfo, parcel, 0)
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
new file mode 100644
index 0000000..411cb09
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedMainComponent
+import android.content.pm.parsing.component.ParsedService
+import android.os.Parcelable
+import java.util.Arrays
+import kotlin.contracts.ExperimentalContracts
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction1
+
+@ExperimentalContracts
+abstract class ParsedMainComponentTest(kClass: KClass<out Parcelable>) :
+ ParsedComponentTest(kClass) {
+
+ final override val subclassBaseParams
+ get() = mainComponentSubclassBaseParams + listOf(
+ ParsedMainComponent::getOrder,
+ ParsedMainComponent::getProcessName,
+ ParsedMainComponent::getSplitName,
+ ParsedMainComponent::isDirectBootAware,
+ ParsedMainComponent::isEnabled,
+ ParsedMainComponent::isExported,
+ )
+
+ abstract val mainComponentSubclassBaseParams: Collection<KFunction1<*, Any?>>
+
+ final override fun subclassExtraParams() = mainComponentSubclassExtraParams() + listOf(
+ getSetByValue(
+ ParsedService::getAttributionTags,
+ ParsedService::setAttributionTags,
+ arrayOf("testAttributionTag"),
+ compare = Arrays::equals
+ ),
+ )
+
+ open fun mainComponentSubclassExtraParams(): Collection<Param?> = emptyList()
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
new file mode 100644
index 0000000..53c862a
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedPermissionGroup
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedPermissionGroupTest : ParsedComponentTest(ParsedPermissionGroup::class) {
+
+ override val defaultImpl = ParsedPermissionGroup()
+ override val creator = ParsedPermissionGroup.CREATOR
+
+ override val subclassBaseParams = listOf(
+ ParsedPermissionGroup::getRequestDetailResourceId,
+ ParsedPermissionGroup::getBackgroundRequestDetailResourceId,
+ ParsedPermissionGroup::getBackgroundRequestResourceId,
+ ParsedPermissionGroup::getRequestRes,
+ ParsedPermissionGroup::getPriority,
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
new file mode 100644
index 0000000..bb63e2e2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedPermission
+import android.content.pm.parsing.component.ParsedPermissionGroup
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedPermissionTest : ParsedComponentTest(ParsedPermission::class) {
+
+ override val defaultImpl = ParsedPermission()
+ override val creator = ParsedPermission.CREATOR
+
+ override val subclassExcludedMethods = listOf(
+ // Utility methods
+ "isAppOp",
+ "isRuntime",
+ "getProtection",
+ "getProtectionFlags",
+ "calculateFootprint",
+ "setKnownCert", // Tested through setKnownCerts
+ )
+ override val subclassBaseParams = listOf(
+ ParsedPermission::getBackgroundPermission,
+ ParsedPermission::getGroup,
+ ParsedPermission::getRequestRes,
+ ParsedPermission::getProtectionLevel,
+ ParsedPermission::isTree,
+ )
+
+ override fun subclassExtraParams() = listOf(
+ getter(ParsedPermission::getKnownCerts, setOf("testCert")),
+ getSetByValue(
+ ParsedPermission::getParsedPermissionGroup,
+ ParsedPermission::setParsedPermissionGroup,
+ ParsedPermissionGroup().apply { name = "test.permission.group" },
+ compare = { first, second -> equalBy(first, second, ParsedPermissionGroup::getName) }
+ ),
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
new file mode 100644
index 0000000..34f46f2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedProcess
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class) {
+
+ override val defaultImpl = ParsedProcess()
+ override val creator = ParsedProcess.CREATOR
+
+ override val excludedMethods = listOf(
+ // Copying method
+ "addStateFrom",
+ )
+
+ override val baseParams = listOf(
+ ParsedProcess::getName,
+ ParsedProcess::getGwpAsanMode,
+ ParsedProcess::getMemtagMode,
+ ParsedProcess::getNativeHeapZeroInitialized,
+ )
+
+ override fun extraParams() = listOf(
+ getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission"))
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
new file mode 100644
index 0000000..e6d5c0f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.PathPermission
+import android.content.pm.parsing.component.ParsedProvider
+import android.os.PatternMatcher
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class) {
+
+ override val defaultImpl = ParsedProvider()
+ override val creator = ParsedProvider.CREATOR
+
+ override val mainComponentSubclassBaseParams = listOf(
+ ParsedProvider::getAuthority,
+ ParsedProvider::isSyncable,
+ ParsedProvider::getReadPermission,
+ ParsedProvider::getWritePermission,
+ ParsedProvider::isGrantUriPermissions,
+ ParsedProvider::isForceUriPermissions,
+ ParsedProvider::isMultiProcess,
+ ParsedProvider::getInitOrder,
+ )
+
+ override fun mainComponentSubclassExtraParams() = listOf(
+ getSetByValue(
+ ParsedProvider::getUriPermissionPatterns,
+ ParsedProvider::setUriPermissionPatterns,
+ PatternMatcher("testPattern", PatternMatcher.PATTERN_LITERAL),
+ transformGet = { it?.singleOrNull() },
+ transformSet = { arrayOf(it) },
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ PatternMatcher::getPath,
+ PatternMatcher::getType
+ )
+ }
+ ),
+ getSetByValue(
+ ParsedProvider::getPathPermissions,
+ ParsedProvider::setPathPermissions,
+ PathPermission(
+ "testPermissionPattern",
+ PatternMatcher.PATTERN_LITERAL,
+ "test.READ_PERMISSION",
+ "test.WRITE_PERMISSION"
+ ),
+ transformGet = { it?.singleOrNull() },
+ transformSet = { arrayOf(it) },
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ PatternMatcher::getPath,
+ PatternMatcher::getType,
+ PathPermission::getReadPermission,
+ PathPermission::getWritePermission,
+ )
+ }
+ )
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
new file mode 100644
index 0000000..5530184
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedService
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedServiceTest : ParsedMainComponentTest(ParsedService::class) {
+
+ override val defaultImpl = ParsedService()
+ override val creator = ParsedService.CREATOR
+
+ override val mainComponentSubclassBaseParams = listOf(
+ ParsedService::getForegroundServiceType,
+ ParsedService::getPermission,
+ )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
new file mode 100644
index 0000000..1131c72
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedUsesPermission
+import android.os.Parcelable
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedUsesPermissionTest : ParcelableComponentTest(ParsedUsesPermission::class) {
+
+ override val defaultImpl = ParsedUsesPermission("", 0)
+ override val creator = ParsedUsesPermission.CREATOR
+
+ override val baseParams = listOf(
+ ParsedUsesPermission::getName,
+ ParsedUsesPermission::getUsesPermissionFlags
+ )
+
+ override fun initialObject() = ParsedUsesPermission("", 0)
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithCreator.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithCreator.java
new file mode 100644
index 0000000..581d2b2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithCreator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling.java;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+public class TestSubWithCreator extends TestSuperClass {
+
+ @NonNull
+ public static final Parcelable.Creator<TestSubWithCreator> CREATOR =
+ new Parcelable.Creator<TestSubWithCreator>() {
+ @Override
+ public TestSubWithCreator createFromParcel(Parcel source) {
+ return new TestSubWithCreator(source);
+ }
+
+ @Override
+ public TestSubWithCreator[] newArray(int size) {
+ return new TestSubWithCreator[size];
+ }
+ };
+
+ @Nullable
+ private String subString;
+
+ public TestSubWithCreator() {
+ }
+
+ public TestSubWithCreator(@NonNull Parcel in) {
+ super(in);
+ subString = in.readString();
+ }
+
+ public void writeSubToParcel(@NonNull Parcel parcel, int flags) {
+ super.writeToParcel(parcel, flags);
+ parcel.writeString(subString);
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithoutCreator.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithoutCreator.java
new file mode 100644
index 0000000..4264a11
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithoutCreator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling.java;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+public class TestSubWithoutCreator extends TestSuperClass {
+
+ @Nullable
+ private String subString;
+
+ public TestSubWithoutCreator() {
+ }
+
+ public TestSubWithoutCreator(@NonNull Parcel in) {
+ super(in);
+ subString = in.readString();
+ }
+
+ public void writeSubToParcel(@NonNull Parcel parcel, int flags) {
+ super.writeToParcel(parcel, flags);
+ parcel.writeString(subString);
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java
new file mode 100644
index 0000000..a009786
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.parsing.parcelling.java;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genAidl = false,
+ genParcelable = true, genConstructor = false)
+public class TestSuperClass implements Parcelable {
+
+ @Nullable
+ private String superString;
+
+ public TestSuperClass() {
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public @Nullable String getSuperString() {
+ return superString;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull TestSuperClass setSuperString(@NonNull String value) {
+ superString = value;
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (superString != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (superString != null) dest.writeString(superString);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected TestSuperClass(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String _superString = (flg & 0x1) == 0 ? null : in.readString();
+
+ this.superString = _superString;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TestSuperClass> CREATOR
+ = new Parcelable.Creator<TestSuperClass>() {
+ @Override
+ public TestSuperClass[] newArray(int size) {
+ return new TestSuperClass[size];
+ }
+
+ @Override
+ public TestSuperClass createFromParcel(@NonNull android.os.Parcel in) {
+ return new TestSuperClass(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1624381019144L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java",
+ inputSignatures = "private @android.annotation.Nullable java.lang.String superString\nclass TestSuperClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genAidl=false, genParcelable=true, genConstructor=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt
new file mode 100644
index 0000000..afb18f5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.util
+
+import com.google.common.truth.Expect
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Wrapper for [Expect] which supports ignoring any failures. This should be used with caution, but
+ * it allows a base test to be written which doesn't switch success/failure in the test itself,
+ * preventing any logic errors from causing the test to accidentally succeed.
+ */
+internal class IgnoreableExpect : TestRule {
+
+ val expect = Expect.create()
+
+ private var ignore = false
+
+ override fun apply(base: Statement?, description: Description?): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ ignore = false
+ try {
+ expect.apply(base, description).evaluate()
+ } catch (t: Throwable) {
+ if (!ignore) {
+ throw t
+ }
+ }
+ }
+ }
+ }
+
+ fun ignore() {
+ ignore = true
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
new file mode 100644
index 0000000..00e3bf1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.hdmi.RequestSadAction.RequestSadCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class RequestSadActionTest {
+
+ private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
+ private static final ArrayList<Integer> CODECS_TO_QUERY_1 = new ArrayList<Integer>(
+ Arrays.asList(Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD,
+ Constants.AUDIO_CODEC_MPEG1, Constants.AUDIO_CODEC_MP3));
+ private static final ArrayList<Integer> CODECS_TO_QUERY_2 = new ArrayList<Integer>(
+ Arrays.asList(Constants.AUDIO_CODEC_MPEG2, Constants.AUDIO_CODEC_AAC,
+ Constants.AUDIO_CODEC_DTS, Constants.AUDIO_CODEC_ATRAC));
+ private static final ArrayList<Integer> CODECS_TO_QUERY_3 = new ArrayList<Integer>(
+ Arrays.asList(Constants.AUDIO_CODEC_ONEBITAUDIO, Constants.AUDIO_CODEC_DDP,
+ Constants.AUDIO_CODEC_DTSHD, Constants.AUDIO_CODEC_TRUEHD));
+ private static final ArrayList<Integer> CODECS_TO_QUERY_4 = new ArrayList<Integer>(
+ Arrays.asList(Constants.AUDIO_CODEC_DST, Constants.AUDIO_CODEC_WMAPRO,
+ Constants.AUDIO_CODEC_MAX));
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
+ private FakeNativeWrapper mNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private List<byte[]> mSupportedSads;
+ private RequestSadCallback mCallback =
+ new RequestSadCallback() {
+ @Override
+ public void onRequestSadDone(
+ List<byte[]> supportedSads) {
+ mSupportedSads = supportedSads;
+ }
+ };
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private IThermalService mIThermalServiceMock;
+
+ private static byte[] concatenateSads(List<byte[]> sads) {
+ byte[] concatenatedSads = new byte[sads.size() * 3];
+ for (int i = 0; i < sads.size(); i++) {
+ for (int j = 0; j < 3; j++) {
+ concatenatedSads[3 * i + j] = sads.get(i)[j];
+ }
+ }
+ return concatenatedSads;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ mMyLooper = mTestLooper.getLooper();
+
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+ Collections.emptyList()) {
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+
+ @Override
+ void wakeUp() {
+ }
+
+ @Override
+ protected void writeStringSystemProperty(String key, String value) {
+ // do nothing
+ }
+
+ @Override
+ boolean isPowerStandbyOrTransient() {
+ return false;
+ }
+
+ @Override
+ protected PowerManager getPowerManager() {
+ return new PowerManager(context, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(mMyLooper));
+ }
+ };
+
+ mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
+ mHdmiCecLocalDeviceTv.init();
+ mHdmiControlService.setIoLooper(mMyLooper);
+ mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mLocalDevices.add(mHdmiCecLocalDeviceTv);
+ mHdmiControlService.initService();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mNativeWrapper.setPhysicalAddress(0x0000);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+ }
+
+ @Test
+ public void noResponse_queryAgain_emptyResult() {
+ RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+ mCallback);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mSupportedSads.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void featureAbort_dontQueryAgain_emptyResult() {
+ RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+ mCallback);
+ action.start();
+ mTestLooper.dispatchAll();
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
+ Constants.ABORT_INVALID_OPERAND);
+
+ HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(featureAbort);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(featureAbort);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(featureAbort);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ action.processCommand(featureAbort);
+ mTestLooper.dispatchAll();
+
+ assertThat(mSupportedSads.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void allSupported_completeResult() {
+ RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+ mCallback);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_1 = new byte[]{
+ 0x01, 0x18, 0x4A,
+ 0x02, 0x64, 0x5A,
+ 0x03, 0x4B, 0x00,
+ 0x04, 0x20, 0x0A};
+ HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response1);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_2 = new byte[]{
+ 0x05, 0x18, 0x4A,
+ 0x06, 0x64, 0x5A,
+ 0x07, 0x4B, 0x00,
+ 0x08, 0x20, 0x0A};
+ HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response2);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_3 = new byte[]{
+ 0x09, 0x18, 0x4A,
+ 0x0A, 0x64, 0x5A,
+ 0x0B, 0x4B, 0x00,
+ 0x0C, 0x20, 0x0A};
+ HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response3);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_4 = new byte[]{
+ 0x0D, 0x18, 0x4A,
+ 0x0E, 0x64, 0x5A,
+ 0x0F, 0x4B, 0x00};
+ HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ action.processCommand(response4);
+ mTestLooper.dispatchAll();
+
+ assertThat(mSupportedSads.size()).isEqualTo(15);
+ assertThat(Arrays.equals(sadsToRespond_1,
+ concatenateSads(mSupportedSads.subList(0, 4)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_2,
+ concatenateSads(mSupportedSads.subList(4, 8)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_3,
+ concatenateSads(mSupportedSads.subList(8, 12)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_4,
+ concatenateSads(mSupportedSads.subList(12, 15)))).isTrue();
+ }
+
+ @Test
+ public void subsetSupported_subsetResult() {
+ RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+ mCallback);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_1 = new byte[]{
+ 0x01, 0x18, 0x4A,
+ 0x03, 0x4B, 0x00,
+ 0x04, 0x20, 0x0A};
+ HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response1);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_2 = new byte[]{
+ 0x08, 0x20, 0x0A};
+ HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response2);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_3 = new byte[]{
+ 0x09, 0x18, 0x4A,
+ 0x0A, 0x64, 0x5A,
+ 0x0B, 0x4B, 0x00,
+ 0x0C, 0x20, 0x0A};
+ HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response3);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_4 = new byte[]{
+ 0x0F, 0x4B, 0x00};
+ HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ action.processCommand(response4);
+ mTestLooper.dispatchAll();
+
+ assertThat(mSupportedSads.size()).isEqualTo(9);
+ assertThat(Arrays.equals(sadsToRespond_1,
+ concatenateSads(mSupportedSads.subList(0, 3)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_2,
+ concatenateSads(mSupportedSads.subList(3, 4)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_3,
+ concatenateSads(mSupportedSads.subList(4, 8)))).isTrue();
+ assertThat(Arrays.equals(sadsToRespond_4,
+ concatenateSads(mSupportedSads.subList(8, 9)))).isTrue();
+ }
+
+ @Test
+ public void invalidCodecs_emptyResults() {
+ RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+ mCallback);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_1 = new byte[]{
+ 0x20, 0x18, 0x4A,
+ 0x21, 0x64, 0x5A,
+ 0x22, 0x4B, 0x00,
+ 0x23, 0x20, 0x0A};
+ HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response1);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_2 = new byte[]{
+ 0x24, 0x18, 0x4A,
+ 0x25, 0x64, 0x5A,
+ 0x26, 0x4B, 0x00,
+ 0x27, 0x20, 0x0A};
+ HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response2);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_3 = new byte[]{
+ 0x28, 0x18, 0x4A,
+ 0x29, 0x64, 0x5A,
+ 0x2A, 0x4B, 0x00,
+ 0x2B, 0x20, 0x0A};
+ HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response3);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_4 = new byte[]{
+ 0x2C, 0x18, 0x4A,
+ 0x2D, 0x64, 0x5A,
+ 0x2E, 0x4B, 0x00};
+ HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ action.processCommand(response4);
+ mTestLooper.dispatchAll();
+
+ assertThat(mSupportedSads.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void invalidMessageLength_queryAgain() {
+ RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+ mCallback);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_1 = new byte[]{
+ 0x01, 0x18,
+ 0x02, 0x64, 0x5A,
+ 0x03, 0x4B, 0x00,
+ 0x04, 0x20, 0x0A};
+ HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response1);
+ mTestLooper.dispatchAll();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_2 = new byte[]{
+ 0x05, 0x18, 0x4A,
+ 0x06, 0x64, 0x5A,
+ 0x07,
+ 0x08, 0x20, 0x0A};
+ HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response2);
+ mTestLooper.dispatchAll();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_3 = new byte[0];
+ HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response3);
+ mTestLooper.dispatchAll();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_AUDIO_SYSTEM,
+ CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+ byte[] sadsToRespond_4 = new byte[]{
+ 0x0D, 0x18, 0x4A,
+ 0x0E, 0x64, 0x5A,
+ 0x0F, 0x4B};
+ HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+ Constants.ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ mNativeWrapper.clearResultMessages();
+ action.processCommand(response4);
+ mTestLooper.dispatchAll();
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mSupportedSads.size()).isEqualTo(0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index e8c5fb3..38b98ca 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -949,7 +949,7 @@
.addConfigPreference(new ConfigurationInfo())
.addReqFeature(new FeatureInfo())
.addFeatureGroup(new FeatureGroupInfo())
- .setCompileSdkVersionCodename("foo23")
+ .setCompileSdkVersionCodeName("foo23")
.setCompileSdkVersion(100)
.setOverlayCategory("foo24")
.setOverlayIsStatic(true)
diff --git a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
index 65a9759..4a25323 100644
--- a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
@@ -19,19 +19,31 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Clock;
+import java.time.ZoneOffset;
+
/** Test that the ledger records transactions correctly. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LedgerTest {
+ @Before
+ public void setUp() {
+ TareUtils.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+ }
+
@Test
public void testInitialState() {
final Ledger ledger = new Ledger();
@@ -72,4 +84,45 @@
assertEquals(1, ledger.get24HourSum(1, 27 * HOUR_IN_MILLIS));
assertEquals(0, ledger.get24HourSum(1, 28 * HOUR_IN_MILLIS));
}
+
+ @Test
+ public void testRemoveOldTransactions() {
+ final Ledger ledger = new Ledger();
+ ledger.removeOldTransactions(24 * HOUR_IN_MILLIS);
+ assertNull(ledger.getEarliestTransaction());
+
+ final long now = getCurrentTimeMillis();
+ Ledger.Transaction transaction1 = new Ledger.Transaction(
+ now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800);
+ Ledger.Transaction transaction2 = new Ledger.Transaction(
+ now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600);
+ Ledger.Transaction transaction3 = new Ledger.Transaction(
+ now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600);
+ // Instant event
+ Ledger.Transaction transaction4 = new Ledger.Transaction(
+ now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500);
+ // Recent event
+ Ledger.Transaction transaction5 = new Ledger.Transaction(
+ now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400);
+ ledger.recordTransaction(transaction1);
+ ledger.recordTransaction(transaction2);
+ ledger.recordTransaction(transaction3);
+ ledger.recordTransaction(transaction4);
+ ledger.recordTransaction(transaction5);
+
+ assertEquals(transaction1, ledger.getEarliestTransaction());
+ ledger.removeOldTransactions(24 * HOUR_IN_MILLIS);
+ assertEquals(transaction2, ledger.getEarliestTransaction());
+ ledger.removeOldTransactions(23 * HOUR_IN_MILLIS);
+ assertEquals(transaction3, ledger.getEarliestTransaction());
+ // Shouldn't delete transaction3 yet since there's still a piece of it within the min age
+ // window.
+ ledger.removeOldTransactions(21 * HOUR_IN_MILLIS + 30 * MINUTE_IN_MILLIS);
+ assertEquals(transaction3, ledger.getEarliestTransaction());
+ // Instant event should be removed as soon as we hit the exact threshold.
+ ledger.removeOldTransactions(20 * HOUR_IN_MILLIS);
+ assertEquals(transaction5, ledger.getEarliestTransaction());
+ ledger.removeOldTransactions(0);
+ assertNull(ledger.getEarliestTransaction());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 41f2246..54406c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1616,6 +1616,21 @@
}
@Test
+ public void testRemoveImmediatelyWithFinishingActivity() throws RemoteException {
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
+ activity.makeFinishingLocked();
+ assertTrue(activity.finishing);
+
+ activity.getTask().removeImmediately("test");
+
+ verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.appToken),
+ isA(DestroyActivityItem.class));
+ assertFalse(wpc.hasActivities());
+ assertEquals(DESTROYING, activity.getState());
+ }
+
+ @Test
public void testRemoveFromHistory() {
final ActivityRecord activity = createActivityWithTask();
final Task rootTask = activity.getRootTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index aa56183..17288c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -53,9 +53,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW;
import static com.google.common.truth.Truth.assertThat;
@@ -895,36 +892,6 @@
assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
- @Test
- public void testAdjustImeInsetsVisibilityWhenTaskSwitchIsAnimating() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
- final InsetsStateController controller = mDisplayContent.getInsetsStateController();
- controller.getImeSourceProvider().setWindow(mImeWindow, null, null);
-
- // Simulate app requests IME with updating all windows Insets State when IME is above app.
- mDisplayContent.setImeLayeringTarget(app);
- mDisplayContent.setImeInputTarget(app);
- assertTrue(mDisplayContent.shouldImeAttachedToApp());
- controller.getImeSourceProvider().scheduleShowImePostLayout(app);
- controller.getImeSourceProvider().getSource().setVisible(true);
- controller.updateAboveInsetsState(mImeWindow, false);
-
- // Simulate task switching animation happens when switching app to app2.
- spyOn(app);
- spyOn(app2);
- doReturn(true).when(app).isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS);
- doReturn(true).when(app2).isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS);
- app.mActivityRecord.mLastImeShown = true;
-
- // Verify the IME insets is visible on app, but not for app2 during task animating.
- InsetsState stateApp = app.getInsetsState();
- InsetsState stateApp2 = app2.getInsetsState();
- assertTrue(stateApp.getSource(ITYPE_IME).isVisible());
- assertFalse(stateApp2.getSource(ITYPE_IME).isVisible());
- }
-
@UseTestDisplay(addWindows = { W_ACTIVITY })
@Test
public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 965f126..734172f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -36,6 +36,7 @@
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
+import android.media.AudioManagerInternal;
import android.media.permission.Identity;
import android.media.permission.PermissionUtil;
import android.os.Binder;
@@ -44,6 +45,7 @@
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
@@ -275,6 +277,7 @@
LocalServices.getService(PermissionManagerServiceInternal.class)
.setHotwordDetectionServiceProvider(null);
mIdentity = null;
+ updateServiceUidForAudioPolicy(Process.INVALID_UID);
}
mCancellationTaskFuture.cancel(/* may interrupt */ true);
if (mAudioFlinger != null) {
@@ -893,6 +896,8 @@
connection.run(service -> service.ping(new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle bundle) throws RemoteException {
+ // TODO: Exit if the service has been unbound already (though there's a very low
+ // chance this happens).
if (DEBUG) {
Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
}
@@ -902,10 +907,21 @@
LocalServices.getService(PermissionManagerServiceInternal.class)
.setHotwordDetectionServiceProvider(() -> uid);
mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
+ updateServiceUidForAudioPolicy(uid);
}
}));
}
+ private void updateServiceUidForAudioPolicy(int uid) {
+ mScheduledExecutorService.execute(() -> {
+ final AudioManagerInternal audioManager =
+ LocalServices.getService(AudioManagerInternal.class);
+ if (audioManager != null) {
+ audioManager.setHotwordDetectionServiceUid(uid);
+ }
+ });
+ }
+
private static void bestEffortClose(Closeable closeable) {
try {
closeable.close();
diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp
new file mode 100644
index 0000000..131611d
--- /dev/null
+++ b/tests/InputMethodStressTest/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "InputMethodStressTest",
+ srcs: ["src/**/*.java"],
+ libs: ["android.test.runner"],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.uiautomator_uiautomator",
+ "compatibility-device-util-axt",
+ "platform-test-annotations",
+ "truth-prebuilt",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ sdk_version: "31",
+}
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml
new file mode 100644
index 0000000..e5d6518
--- /dev/null
+++ b/tests/InputMethodStressTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.inputmethod.stresstest">
+
+ <application>
+ <activity android:name=".TestActivity"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.inputmethod.stresstest">
+ </instrumentation>
+</manifest>
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
new file mode 100644
index 0000000..b194010
--- /dev/null
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="InputMethod integration/regression test">
+ <option name="test-suite-tag" value="apct" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="InputMethodStressTest.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.inputmethod.stresstest" />
+ </test>
+</configuration>
diff --git a/tests/InputMethodStressTest/OWNERS b/tests/InputMethodStressTest/OWNERS
new file mode 100644
index 0000000..6bb4b17
--- /dev/null
+++ b/tests/InputMethodStressTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 34867
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/ImeOpenCloseStressTest.java
new file mode 100644
index 0000000..5427fd8
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/ImeOpenCloseStressTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.stresstest;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.platform.test.annotations.RootPermissionTest;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RootPermissionTest
+@RunWith(AndroidJUnit4.class)
+public class ImeOpenCloseStressTest {
+
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final int NUM_TEST_ITERATIONS = 100;
+
+ private Instrumentation mInstrumentation;
+
+ @Test
+ public void test() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(mInstrumentation.getContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ TestActivity activity = (TestActivity) mInstrumentation.startActivitySync(intent);
+ eventually(() -> assertThat(callOnMainSync(activity::hasWindowFocus)).isTrue(), TIMEOUT);
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ mInstrumentation.runOnMainSync(activity::showIme);
+ eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isTrue(), TIMEOUT);
+ mInstrumentation.runOnMainSync(activity::hideIme);
+ eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isFalse(), TIMEOUT);
+ }
+ }
+
+ private <V> V callOnMainSync(Callable<V> callable) {
+ AtomicReference<V> result = new AtomicReference<>();
+ AtomicReference<Exception> thrownException = new AtomicReference<>();
+ mInstrumentation.runOnMainSync(() -> {
+ try {
+ result.set(callable.call());
+ } catch (Exception e) {
+ thrownException.set(e);
+ }
+ });
+ if (thrownException.get() != null) {
+ throw new RuntimeException("Exception thrown from Main thread", thrownException.get());
+ }
+ return result.get();
+ }
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/TestActivity.java b/tests/InputMethodStressTest/src/com/android/inputmethod/TestActivity.java
new file mode 100644
index 0000000..7baf037
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/TestActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.stresstest;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowInsets;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+public class TestActivity extends Activity {
+
+ private EditText mEditText;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout rootView = new LinearLayout(this);
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ mEditText = new EditText(this);
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
+ }
+
+ public boolean hasWindowFocus() {
+ return mEditText.hasWindowFocus();
+ }
+
+ public boolean isImeShown() {
+ WindowInsets insets = mEditText.getRootWindowInsets();
+ return insets.isVisible(WindowInsets.Type.ime());
+ }
+
+ public void showIme() {
+ mEditText.requestFocus();
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.showSoftInput(mEditText, 0);
+ }
+
+ public void hideIme() {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+ }
+}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 05c46ed..7a668a5 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -54,7 +54,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -203,194 +202,6 @@
}
/**
- * Test that multiple available rollbacks are properly persisted.
- */
- @Test
- public void testAvailableRollbackPersistence() throws Exception {
- try {
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.TEST_MANAGE_ROLLBACKS);
-
- RollbackManager rm = RollbackUtils.getRollbackManager();
-
- Uninstall.packages(TestApp.A);
- Install.single(TestApp.A1).commit();
- Install.single(TestApp.A2).setEnableRollback().commit();
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
- Uninstall.packages(TestApp.B);
- Install.single(TestApp.B1).commit();
- Install.single(TestApp.B2).setEnableRollback().commit();
- assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
- // Both test apps should now be available for rollback.
- RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A);
- assertThat(rollbackA).isNotNull();
- assertThat(rollbackA).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1));
-
- RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
- assertThat(rollbackB).isNotNull();
- assertThat(rollbackB).packagesContainsExactly(
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- // Reload the persisted data.
- rm.reloadPersistedData();
-
- // The apps should still be available for rollback.
- rollbackA = waitForAvailableRollback(TestApp.A);
- assertThat(rollbackA).isNotNull();
- assertThat(rollbackA).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1));
-
- rollbackB = waitForAvailableRollback(TestApp.B);
- assertThat(rollbackB).isNotNull();
- assertThat(rollbackB).packagesContainsExactly(
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- // Rollback of B should not rollback A
- RollbackUtils.rollback(rollbackB.getRollbackId());
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
- } finally {
- InstallUtils.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Test that available multi-package rollbacks are properly persisted.
- */
- @Test
- public void testAvailableMultiPackageRollbackPersistence() throws Exception {
- try {
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.TEST_MANAGE_ROLLBACKS);
-
- RollbackManager rm = RollbackUtils.getRollbackManager();
-
- Uninstall.packages(TestApp.A, TestApp.B);
- Install.multi(TestApp.A1, TestApp.B1).commit();
- Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
-
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
- // The app should now be available for rollback.
- RollbackInfo availableA = waitForAvailableRollback(TestApp.A);
- assertThat(availableA).isNotNull();
- assertThat(availableA).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- RollbackInfo availableB = waitForAvailableRollback(TestApp.B);
- assertThat(availableB).isNotNull();
- assertThat(availableB).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- // Assert they're both the same rollback
- assertThat(availableA).hasRollbackId(availableB.getRollbackId());
-
- // Reload the persisted data.
- rm.reloadPersistedData();
-
- // The apps should still be available for rollback.
- availableA = waitForAvailableRollback(TestApp.A);
- assertThat(availableA).isNotNull();
- assertThat(availableA).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- availableB = waitForAvailableRollback(TestApp.B);
- assertThat(availableB).isNotNull();
- assertThat(availableB).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- // Rollback of B should rollback A as well
- RollbackUtils.rollback(availableB.getRollbackId());
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
- assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
-
- RollbackInfo committedA = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TestApp.A);
- assertThat(committedA).isNotNull();
- assertThat(committedA).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- RollbackInfo committedB = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TestApp.A);
- assertThat(committedB).isNotNull();
- assertThat(committedB).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- // Assert they're both the same rollback
- assertThat(committedA).hasRollbackId(committedB.getRollbackId());
- assertThat(committedA).hasRollbackId(availableA.getRollbackId());
- } finally {
- InstallUtils.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Test that recently committed rollback data is properly persisted.
- */
- @Test
- public void testRecentlyCommittedRollbackPersistence() throws Exception {
- try {
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.TEST_MANAGE_ROLLBACKS);
-
- RollbackManager rm = RollbackUtils.getRollbackManager();
-
- Uninstall.packages(TestApp.A);
- Install.single(TestApp.A1).commit();
- Install.single(TestApp.A2).setEnableRollback().commit();
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
- // The app should now be available for rollback.
- RollbackInfo available = waitForAvailableRollback(TestApp.A);
- assertThat(available).isNotNull();
-
- // Roll back the app.
- TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo",
- /*versionCode*/ 42, /*isApex*/ false);
- RollbackUtils.rollback(available.getRollbackId(), cause);
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-
- // Verify the recent rollback has been recorded.
- RollbackInfo committed = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TestApp.A);
- assertThat(committed).isNotNull();
- assertThat(committed).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1));
- assertThat(committed).causePackagesContainsExactly(cause);
-
- // Reload the persisted data.
- rm.reloadPersistedData();
-
- // Verify the recent rollback is still recorded.
- committed = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TestApp.A);
- assertThat(committed).isNotNull();
- assertThat(committed).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1));
- assertThat(committed).causePackagesContainsExactly(cause);
- assertThat(committed).hasRollbackId(available.getRollbackId());
- } finally {
- InstallUtils.dropShellPermissionIdentity();
- }
- }
-
- /**
* Test the scheduling aspect of rollback expiration.
*/
@Test
@@ -739,203 +550,6 @@
}
}
- /**
- * Test that the MANAGE_ROLLBACKS permission is required to call
- * RollbackManager APIs.
- */
- @Test
- public void testManageRollbacksPermission() throws Exception {
- // We shouldn't be allowed to call any of the RollbackManager APIs
- // without the MANAGE_ROLLBACKS permission.
- RollbackManager rm = RollbackUtils.getRollbackManager();
-
- try {
- rm.getAvailableRollbacks();
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
-
- try {
- rm.getRecentlyCommittedRollbacks();
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
-
- try {
- // TODO: What if the implementation checks arguments for non-null
- // first? Then this test isn't valid.
- rm.commitRollback(0, Collections.emptyList(), null);
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
-
- try {
- rm.reloadPersistedData();
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
-
- try {
- rm.expireRollbackForPackage(TestApp.A);
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
- }
-
- /**
- * Test that you cannot enable rollback for a package without the
- * MANAGE_ROLLBACKS permission.
- */
- @Test
- public void testEnableRollbackPermission() throws Exception {
- try {
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES);
-
- Uninstall.packages(TestApp.A);
- Install.single(TestApp.A1).commit();
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-
- Install.single(TestApp.A2).setEnableRollback().commit();
-
- // We expect v2 of the app was installed, but rollback has not
- // been enabled.
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(
- getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
- } finally {
- InstallUtils.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Test that you cannot enable rollback for a non-module package when
- * holding the MANAGE_ROLLBACKS permission.
- */
- @Test
- public void testNonModuleEnableRollback() throws Exception {
- try {
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.MANAGE_ROLLBACKS);
-
- Uninstall.packages(TestApp.A);
- Install.single(TestApp.A1).commit();
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-
- Install.single(TestApp.A2).setEnableRollback().commit();
-
- // We expect v2 of the app was installed, but rollback has not
- // been enabled because the test app is not a module.
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(
- getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
- } finally {
- InstallUtils.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Test rollback of multi-package installs is implemented.
- */
- @Test
- public void testMultiPackage() throws Exception {
- try {
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackUtils.getRollbackManager();
-
- // Prep installation of the test apps.
- Uninstall.packages(TestApp.A, TestApp.B);
- Install.multi(TestApp.A1, TestApp.B1).commit();
- processUserData(TestApp.A);
- processUserData(TestApp.B);
- Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
- processUserData(TestApp.A);
- processUserData(TestApp.B);
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
- // TestApp.A should now be available for rollback.
- RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
- assertThat(rollback).isNotNull();
- assertThat(rollback).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- // Rollback the app. It should cause both test apps to be rolled
- // back.
- RollbackUtils.rollback(rollback.getRollbackId());
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
- assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
-
- // We should see recent rollbacks listed for both A and B.
- Thread.sleep(1000);
- RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TestApp.A);
-
- RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TestApp.B);
- assertThat(rollback).packagesContainsExactly(
- Rollback.from(TestApp.A2).to(TestApp.A1),
- Rollback.from(TestApp.B2).to(TestApp.B1));
-
- assertThat(rollbackA).hasRollbackId(rollbackB.getRollbackId());
-
- processUserData(TestApp.A);
- processUserData(TestApp.B);
- } finally {
- InstallUtils.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Test failure to enable rollback for multi-package installs.
- * If any one of the packages fail to enable rollback, we shouldn't enable
- * rollback for any package.
- */
- @Test
- public void testMultiPackageEnableFail() throws Exception {
- try {
- InstallUtils.adoptShellPermissionIdentity(
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackUtils.getRollbackManager();
-
- Uninstall.packages(TestApp.A, TestApp.B);
- Install.single(TestApp.A1).commit();
- // We should fail to enable rollback here because TestApp B is not
- // already installed.
- Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
-
- assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
- assertThat(getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TestApp.A)).isNull();
- assertThat(getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TestApp.B)).isNull();
- } finally {
- InstallUtils.dropShellPermissionIdentity();
- }
- }
-
@Test
@Ignore("b/120200473")
/**
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index ea886db..0bbde62 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -27,7 +27,7 @@
static ApiVersion sDevelopmentSdkLevel = 10000;
static const auto sDevelopmentSdkCodeNames =
- std::unordered_set<StringPiece>({"Q", "R", "S", "Tiramisu"});
+ std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu"});
static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
{0x021c, 1},
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 5736a80..900c214 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -30,7 +30,8 @@
CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
- CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY
+ CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
+ CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED
)
override val api: Int
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
new file mode 100644
index 0000000..641f337
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/**
+ * Lint Detector that finds issues with improper usages of the non-user getter methods of Settings
+ */
+@Suppress("UnstableApiUsage")
+class CallingSettingsNonUserGetterMethodsDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableMethodNames(): List<String> = listOf(
+ "getString",
+ "getInt",
+ "getLong",
+ "getFloat"
+ )
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ val evaluator = context.evaluator
+ if (evaluator.isMemberInClass(method, "android.provider.Settings.Secure") ||
+ evaluator.isMemberInClass(method, "android.provider.Settings.System")
+ ) {
+ val message = getIncidentMessageNonUserGetterMethods(getMethodSignature(method))
+ context.report(ISSUE_NON_USER_GETTER_CALLED, node, context.getLocation(node), message)
+ }
+ }
+
+ private fun getMethodSignature(method: PsiMethod) =
+ method.containingClass
+ ?.qualifiedName
+ ?.let { "$it#${method.name}" }
+ ?: method.name
+
+ companion object {
+ @JvmField
+ val ISSUE_NON_USER_GETTER_CALLED: Issue = Issue.create(
+ id = "NonUserGetterCalled",
+ briefDescription = "Non-ForUser Getter Method called to Settings",
+ explanation = """
+ System process should not call the non-ForUser getter methods of \
+ `Settings.Secure` or `Settings.System`. For example, instead of \
+ `Settings.Secure.getInt()`, use `Settings.Secure.getIntForUser()` instead. \
+ This will make sure that the correct Settings value is retrieved.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ CallingSettingsNonUserGetterMethodsDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ fun getIncidentMessageNonUserGetterMethods(methodSignature: String) =
+ "`$methodSignature()` called from system process. " +
+ "Please call `${methodSignature}ForUser()` instead. "
+ }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
new file mode 100644
index 0000000..1034029
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class CallingSettingsNonUserGetterMethodsIssueDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = CallingSettingsNonUserGetterMethodsDetector()
+
+ override fun getIssues(): List<Issue> = listOf(
+ CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED
+ )
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ fun testDoesNotDetectIssues() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.provider.Settings.Secure;
+ public class TestClass1 {
+ private void testMethod(Context context) {
+ final int value = Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.KEY1, 0, 0);
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testDetectsNonUserGetterCalledFromSecure() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.provider.Settings.Secure;
+ public class TestClass1 {
+ private void testMethod(Context context) {
+ final int value = Secure.getInt(context.getContentResolver(),
+ Settings.Secure.KEY1);
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: \
+ android.provider.Settings.Secure#getInt() called from system process. \
+ Please call android.provider.Settings.Secure#getIntForUser() instead. \
+ [NonUserGetterCalled]
+ final int value = Secure.getInt(context.getContentResolver(),
+ ^
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+ fun testDetectsNonUserGetterCalledFromSystem() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.provider.Settings.System;
+ public class TestClass1 {
+ private void testMethod(Context context) {
+ final float value = System.getFloat(context.getContentResolver(),
+ Settings.System.KEY1);
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: \
+ android.provider.Settings.System#getFloat() called from system process. \
+ Please call android.provider.Settings.System#getFloatForUser() instead. \
+ [NonUserGetterCalled]
+ final float value = System.getFloat(context.getContentResolver(),
+ ^
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDetectsNonUserGetterCalledFromSettings() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.provider.Settings;
+ public class TestClass1 {
+ private void testMethod(Context context) {
+ float value = Settings.System.getFloat(context.getContentResolver(),
+ Settings.System.KEY1);
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: \
+ android.provider.Settings.System#getFloat() called from system process. \
+ Please call android.provider.Settings.System#getFloatForUser() instead. \
+ [NonUserGetterCalled]
+ float value = Settings.System.getFloat(context.getContentResolver(),
+ ^
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDetectsNonUserGettersCalledFromSystemAndSecure() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.provider.Settings.Secure;
+ import android.provider.Settings.System;
+ public class TestClass1 {
+ private void testMethod(Context context) {
+ final long value1 = Secure.getLong(context.getContentResolver(),
+ Settings.Secure.KEY1, 0);
+ final String value2 = System.getString(context.getContentResolver(),
+ Settings.System.KEY2);
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:6: Warning: \
+ android.provider.Settings.Secure#getLong() called from system process. \
+ Please call android.provider.Settings.Secure#getLongForUser() instead. \
+ [NonUserGetterCalled]
+ final long value1 = Secure.getLong(context.getContentResolver(),
+ ^
+ src/test/pkg/TestClass1.java:8: Warning: \
+ android.provider.Settings.System#getString() called from system process. \
+ Please call android.provider.Settings.System#getStringForUser() instead. \
+ [NonUserGetterCalled]
+ final String value2 = System.getString(context.getContentResolver(),
+ ^
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ private val SettingsStub: TestFile = java(
+ """
+ package android.provider;
+ public class Settings {
+ public class Secure {
+ float getFloat(ContentResolver cr, String key) {
+ return 0.0f;
+ }
+ long getLong(ContentResolver cr, String key) {
+ return 0l;
+ }
+ int getInt(ContentResolver cr, String key) {
+ return 0;
+ }
+ }
+ public class System {
+ float getFloat(ContentResolver cr, String key) {
+ return 0.0f;
+ }
+ long getLong(ContentResolver cr, String key) {
+ return 0l;
+ }
+ String getString(ContentResolver cr, String key) {
+ return null;
+ }
+ }
+ }
+ """
+ ).indented()
+
+ private val stubs = arrayOf(SettingsStub)
+
+ // Substitutes "backslash + new line" with an empty string to imitate line continuation
+ private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}