diff options
102 files changed, 2363 insertions, 956 deletions
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 5c904c15e706..7f4fd3eff57e 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -617,8 +617,8 @@ flag { } flag { - namespace: "multi_user" name: "logout_user_api" + namespace: "multiuser" description: "Add API to logout user" bug: "350045389" } diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 0964cde5a1f4..c3ec96d17437 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -145,8 +145,17 @@ public final class MessageQueue { } if (Flags.forceConcurrentMessageQueue()) { - sIsProcessAllowedToUseConcurrent = true; - return; + // b/379472827: Robolectric tests use reflection to access MessageQueue.mMessages. + // This is a hack to allow Robolectric tests to use the legacy implementation. + try { + Class.forName("org.robolectric.Robolectric"); + } catch (ClassNotFoundException e) { + // This is not a Robolectric test. + sIsProcessAllowedToUseConcurrent = true; + return; + } + // This is a Robolectric test. + // Continue to the following checks. } final String processName = Process.myProcessName(); diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index a8a22f675e08..b82f278ef7d5 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -216,7 +216,7 @@ public class SystemHealthManager { /** * Gets the maximum number of TIDs this device supports for getting CPU headroom. * <p> - * See {@link CpuHeadroomParams#setTids(int...)}. + * See {@link CpuHeadroomParams.Builder#setTids(int...)}. * * @return the maximum size of TIDs supported * @throws UnsupportedOperationException if the CPU headroom API is unsupported. @@ -288,9 +288,7 @@ public class SystemHealthManager { /** * Gets the range of the calculation window size for CPU headroom. * <p> - * In API version 36, the range will be a superset of [50, 10000]. - * <p> - * See {@link CpuHeadroomParams#setCalculationWindowMillis(int)}. + * See {@link CpuHeadroomParams.Builder#setCalculationWindowMillis(int)}. * * @return the range of the calculation window size supported in milliseconds. * @throws UnsupportedOperationException if the CPU headroom API is unsupported. @@ -310,9 +308,7 @@ public class SystemHealthManager { /** * Gets the range of the calculation window size for GPU headroom. * <p> - * In API version 36, the range will be a superset of [50, 10000]. - * <p> - * See {@link GpuHeadroomParams#setCalculationWindowMillis(int)}. + * See {@link GpuHeadroomParams.Builder#setCalculationWindowMillis(int)}. * * @return the range of the calculation window size supported in milliseconds. * @throws UnsupportedOperationException if the GPU headroom API is unsupported. diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 0476f62ec263..4bd54a173601 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -71,6 +71,19 @@ flag { } flag { + name: "unknown_call_setting_blocked_logging_enabled" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "enable the metrics when blocking certain app installs during an unknown call" + bug: "364535720" + + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "op_enable_mobile_data_by_user" is_exported: true namespace: "permissions" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b97c9b5e83f2..f91056dbce30 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8651,6 +8651,34 @@ public final class Settings { public static final String DOCKED_CLOCK_FACE = "docked_clock_face"; /** + * Setting to indicate that content filters should be enabled on web browsers. + * + * <ul> + * <li>0 = Allow all sites + * <li>1 = Try to block explicit sites + * </ul> + * + * @hide + */ + @Readable + public static final String BROWSER_CONTENT_FILTERS_ENABLED = + "browser_content_filters_enabled"; + + /** + * Setting to indicate that content filters should be enabled in web search engines. + * + * <ul> + * <li>0 = Off + * <li>1 = Filter + * </ul> + * + * @hide + */ + @Readable + public static final String SEARCH_CONTENT_FILTERS_ENABLED = + "search_content_filters_enabled"; + + /** * Set by the system to track if the user needs to see the call to action for * the lockscreen notification policy. * @hide @@ -11132,6 +11160,12 @@ public final class Settings { public static final String DOUBLE_TAP_TO_WAKE = "double_tap_to_wake"; /** + * Controls whether double tap to sleep is enabled. + * @hide + */ + public static final String DOUBLE_TAP_TO_SLEEP = "double_tap_to_sleep"; + + /** * The current assistant component. It could be a voice interaction service, * or an activity that handles ACTION_ASSIST, or empty which means using the default * handling. diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index b4fec416bd5f..3927c713e500 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -86,6 +86,14 @@ flag { } flag { + name: "action_mode_edge_to_edge" + namespace: "windowing_frontend" + description: "Make contextual action bar edge-to-edge" + bug: "379783298" + is_fixed_read_only: true +} + +flag { name: "keyguard_going_away_timeout" namespace: "windowing_frontend" description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting" @@ -501,6 +509,17 @@ flag { description: "Sets Launch powermode for activity launches earlier" bug: "399380676" is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "scramble_snapshot_file_name" + namespace: "windowing_frontend" + description: "Scramble the file name of task snapshot." + bug: "293139053" + is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX } diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 81ca23173457..c6207f9451c2 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -45,11 +45,13 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.locks.ReentrantLock; /** @@ -203,15 +205,6 @@ public class BatteryStatsHistory { BatteryHistoryFragment getLatestFragment(); /** - * Given a fragment, returns the earliest fragment that follows it whose monotonic - * start time falls within the specified range. `startTimeMs` is inclusive, `endTimeMs` - * is exclusive. - */ - @Nullable - BatteryHistoryFragment getNextFragment(BatteryHistoryFragment current, long startTimeMs, - long endTimeMs); - - /** * Acquires a lock on the entire store. */ void lock(); @@ -268,6 +261,60 @@ public class BatteryStatsHistory { void reset(); } + class BatteryHistoryParcelContainer { + private boolean mParcelReadyForReading; + private Parcel mParcel; + private BatteryStatsHistory.BatteryHistoryFragment mFragment; + private long mMonotonicStartTime; + + BatteryHistoryParcelContainer(@NonNull Parcel parcel, long monotonicStartTime) { + mParcel = parcel; + mMonotonicStartTime = monotonicStartTime; + mParcelReadyForReading = true; + } + + BatteryHistoryParcelContainer(@NonNull BatteryHistoryFragment fragment) { + mFragment = fragment; + mMonotonicStartTime = fragment.monotonicTimeMs; + mParcelReadyForReading = false; + } + + @Nullable + Parcel getParcel() { + if (mParcelReadyForReading) { + return mParcel; + } + + Parcel parcel = Parcel.obtain(); + if (readFragmentToParcel(parcel, mFragment)) { + parcel.readInt(); // skip buffer size + mParcel = parcel; + } else { + parcel.recycle(); + } + mParcelReadyForReading = true; + return mParcel; + } + + long getMonotonicStartTime() { + return mMonotonicStartTime; + } + + /** + * Recycles the parcel (if appropriate). Should be called after the parcel has been + * processed by the iterator. + */ + void close() { + if (mParcel != null && mFragment != null) { + mParcel.recycle(); + } + // ParcelContainers are not meant to be reusable. To prevent any unintentional + // access to the parcel after it has been recycled, clear the references to it. + mParcel = null; + mFragment = null; + } + } + private final Parcel mHistoryBuffer; private final HistoryStepDetailsCalculator mStepDetailsCalculator; private final Clock mClock; @@ -447,6 +494,7 @@ public class BatteryStatsHistory { mWritableHistory = writableHistory; if (mWritableHistory != null) { mMutable = false; + mHistoryBufferStartTime = mWritableHistory.mHistoryBufferStartTime; mHistoryMonotonicEndTime = mWritableHistory.mHistoryMonotonicEndTime; } @@ -689,95 +737,66 @@ public class BatteryStatsHistory { } /** - * When iterating history files and history buffer, always start from the lowest numbered - * history file, when reached the mActiveFile (highest numbered history file), do not read from - * mActiveFile, read from history buffer instead because the buffer has more updated data. - * - * @return The parcel that has next record. null if finished all history files and history - * buffer + * Returns all chunks of accumulated history that contain items within the range between + * `startTimeMs` (inclusive) and `endTimeMs` (exclusive) */ - @Nullable - public Parcel getNextParcel(long startTimeMs, long endTimeMs) { - checkImmutable(); + Queue<BatteryHistoryParcelContainer> getParcelContainers(long startTimeMs, long endTimeMs) { + if (mMutable) { + throw new IllegalStateException("Iterating over a mutable battery history"); + } - // First iterate through all records in current parcel. - if (mCurrentParcel != null) { - if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) { - // There are more records in current parcel. - return mCurrentParcel; - } else if (mHistoryBuffer == mCurrentParcel) { - // finished iterate through all history files and history buffer. - return null; - } else if (mHistoryParcels == null - || !mHistoryParcels.contains(mCurrentParcel)) { - // current parcel is from history file. - mCurrentParcel.recycle(); - } + if (endTimeMs == MonotonicClock.UNDEFINED || endTimeMs == 0) { + endTimeMs = Long.MAX_VALUE; } + Queue<BatteryHistoryParcelContainer> containers = new ArrayDeque<>(); + if (mStore != null) { - BatteryHistoryFragment next = mStore.getNextFragment(mCurrentFragment, startTimeMs, - endTimeMs); - while (next != null) { - mCurrentParcel = null; - mCurrentParcelEnd = 0; - final Parcel p = Parcel.obtain(); - if (readFragmentToParcel(p, next)) { - int bufSize = p.readInt(); - int curPos = p.dataPosition(); - mCurrentParcelEnd = curPos + bufSize; - mCurrentParcel = p; - if (curPos < mCurrentParcelEnd) { - mCurrentFragment = next; - return mCurrentParcel; - } - } else { - p.recycle(); + List<BatteryHistoryFragment> fragments = mStore.getFragments(); + for (int i = 0; i < fragments.size(); i++) { + BatteryHistoryFragment fragment = fragments.get(i); + if (fragment.monotonicTimeMs >= endTimeMs) { + break; + } + + if (fragment.monotonicTimeMs >= startTimeMs && fragment != mActiveFragment) { + containers.add(new BatteryHistoryParcelContainer(fragment)); } - next = mStore.getNextFragment(next, startTimeMs, endTimeMs); } } - // mHistoryParcels is created when BatteryStatsImpl object is created from deserialization - // of a parcel, such as Settings app or checkin file. if (mHistoryParcels != null) { - while (mParcelIndex < mHistoryParcels.size()) { - final Parcel p = mHistoryParcels.get(mParcelIndex++); + for (int i = 0; i < mHistoryParcels.size(); i++) { + final Parcel p = mHistoryParcels.get(i); if (!verifyVersion(p)) { continue; } - // skip monotonic time field. - p.readLong(); - // skip monotonic end time field - p.readLong(); + + long monotonicStartTime = p.readLong(); + if (monotonicStartTime >= endTimeMs) { + continue; + } + + long monotonicEndTime = p.readLong(); + if (monotonicEndTime < startTimeMs) { + continue; + } + // skip monotonic size field p.readLong(); + // skip buffer size field + p.readInt(); - final int bufSize = p.readInt(); - final int curPos = p.dataPosition(); - mCurrentParcelEnd = curPos + bufSize; - mCurrentParcel = p; - if (curPos < mCurrentParcelEnd) { - return mCurrentParcel; - } + containers.add(new BatteryHistoryParcelContainer(p, monotonicStartTime)); } } - // finished iterator through history files (except the last one), now history buffer. - if (mHistoryBuffer.dataSize() <= 0) { - // buffer is empty. - return null; - } - mHistoryBuffer.setDataPosition(0); - mCurrentParcel = mHistoryBuffer; - mCurrentParcelEnd = mCurrentParcel.dataSize(); - return mCurrentParcel; - } - - private void checkImmutable() { - if (mMutable) { - throw new IllegalStateException("Iterating over a mutable battery history"); + if (mHistoryBufferStartTime < endTimeMs) { + mHistoryBuffer.setDataPosition(0); + containers.add( + new BatteryHistoryParcelContainer(mHistoryBuffer, mHistoryBufferStartTime)); } + return containers; } /** @@ -818,21 +837,6 @@ public class BatteryStatsHistory { } /** - * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history - * buffer. - */ - public long getHistoryBufferStartTime(Parcel p) { - int pos = p.dataPosition(); - p.setDataPosition(0); - p.readInt(); // Skip the version field - long monotonicTime = p.readLong(); - p.readLong(); // Skip monotonic end time field - p.readLong(); // Skip monotonic size field - p.setDataPosition(pos); - return monotonicTime; - } - - /** * Writes the battery history contents for persistence. */ public void writeSummaryToParcel(Parcel out, boolean inclHistory) { diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index 38398b4bf6f0..09f9b0bf8c7e 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -24,6 +24,7 @@ import android.util.Slog; import android.util.SparseArray; import java.util.Iterator; +import java.util.Queue; /** * An iterator for {@link BatteryStats.HistoryItem}'s. @@ -48,7 +49,10 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor private long mBaseMonotonicTime; private long mBaseTimeUtc; private int mItemIndex = 0; - private int mMaxHistoryItems; + private final int mMaxHistoryItems; + private int mParcelDataPosition; + + private Queue<BatteryStatsHistory.BatteryHistoryParcelContainer> mParcelContainers; public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs, long endTimeMs) { @@ -62,7 +66,11 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor @Override public boolean hasNext() { if (!mNextItemReady) { - advance(); + if (!advance()) { + mHistoryItem = null; + close(); + } + mNextItemReady = true; } return mHistoryItem != null; @@ -75,35 +83,48 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor @Override public BatteryStats.HistoryItem next() { if (!mNextItemReady) { - advance(); + if (!advance()) { + mHistoryItem = null; + close(); + } } mNextItemReady = false; return mHistoryItem; } - private void advance() { - while (true) { - if (mItemIndex > mMaxHistoryItems) { - Slog.wtfStack(TAG, "Number of battery history items is too large: " + mItemIndex); - break; - } + private boolean advance() { + if (mParcelContainers == null) { + mParcelContainers = mBatteryStatsHistory.getParcelContainers(mStartTimeMs, mEndTimeMs); + } - Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs); - if (p == null) { - break; + BatteryStatsHistory.BatteryHistoryParcelContainer container; + while ((container = mParcelContainers.peek()) != null) { + Parcel p = container.getParcel(); + if (p == null || p.dataPosition() >= p.dataSize()) { + container.close(); + mParcelContainers.remove(); + mParcelDataPosition = 0; + continue; } if (!mTimeInitialized) { - mBaseMonotonicTime = mBatteryStatsHistory.getHistoryBufferStartTime(p); + mBaseMonotonicTime = container.getMonotonicStartTime(); mHistoryItem.time = mBaseMonotonicTime; mTimeInitialized = true; } try { readHistoryDelta(p, mHistoryItem); + int dataPosition = p.dataPosition(); + if (dataPosition <= mParcelDataPosition) { + Slog.wtf(TAG, "Corrupted battery history, parcel is not progressing: " + + dataPosition + " of " + p.dataSize()); + return false; + } + mParcelDataPosition = dataPosition; } catch (Throwable t) { Slog.wtf(TAG, "Corrupted battery history", t); - break; + return false; } if (mHistoryItem.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME @@ -111,21 +132,24 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor mBaseTimeUtc = mHistoryItem.currentTime - (mHistoryItem.time - mBaseMonotonicTime); } - mHistoryItem.currentTime = mBaseTimeUtc + (mHistoryItem.time - mBaseMonotonicTime); + if (mHistoryItem.time < mStartTimeMs) { + continue; + } - if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) { - break; + if (mEndTimeMs != 0 && mEndTimeMs != MonotonicClock.UNDEFINED + && mHistoryItem.time >= mEndTimeMs) { + return false; } - if (mHistoryItem.time >= mStartTimeMs) { - mItemIndex++; - mNextItemReady = true; - return; + + if (mItemIndex++ > mMaxHistoryItems) { + Slog.wtfStack(TAG, "Number of battery history items is too large: " + mItemIndex); + return false; } - } - mHistoryItem = null; - mNextItemReady = true; - close(); + mHistoryItem.currentTime = mBaseTimeUtc + (mHistoryItem.time - mBaseMonotonicTime); + return true; + } + return false; } private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index e20a52b24485..3d81e4fc7acd 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -120,6 +120,7 @@ import com.android.internal.view.menu.MenuHelper; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; +import com.android.window.flags.Flags; import java.util.List; import java.util.concurrent.Executor; @@ -1003,7 +1004,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public void onWindowSystemUiVisibilityChanged(int visible) { updateColorViews(null /* insets */, true /* animate */); - if (mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) { + if (!Flags.actionModeEdgeToEdge() + && mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) { updateStatusGuardColor(); } } @@ -1040,7 +1042,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } mFrameOffsets.set(insets.getSystemWindowInsetsAsRect()); insets = updateColorViews(insets, true /* animate */); - insets = updateStatusGuard(insets); + insets = updateActionModeInsets(insets); if (getForeground() != null) { drawableChanged(); } @@ -1592,7 +1594,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } - private WindowInsets updateStatusGuard(WindowInsets insets) { + private WindowInsets updateActionModeInsets(WindowInsets insets) { boolean showStatusGuard = false; // Show the status guard when the non-overlay contextual action bar is showing if (mPrimaryActionModeView != null) { @@ -1608,54 +1610,78 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final Rect rect = mTempRect; // Apply the insets that have not been applied by the contentParent yet. - WindowInsets innerInsets = + final WindowInsets innerInsets = mWindow.mContentParent.computeSystemWindowInsets(insets, rect); - int newTopMargin = innerInsets.getSystemWindowInsetTop(); - int newLeftMargin = innerInsets.getSystemWindowInsetLeft(); - int newRightMargin = innerInsets.getSystemWindowInsetRight(); - - // Must use root window insets for the guard, because the color views consume - // the navigation bar inset if the window does not request LAYOUT_HIDE_NAV - but - // the status guard is attached at the root. - WindowInsets rootInsets = getRootWindowInsets(); - int newGuardLeftMargin = rootInsets.getSystemWindowInsetLeft(); - int newGuardRightMargin = rootInsets.getSystemWindowInsetRight(); - - if (mlp.topMargin != newTopMargin || mlp.leftMargin != newLeftMargin - || mlp.rightMargin != newRightMargin) { - mlpChanged = true; - mlp.topMargin = newTopMargin; - mlp.leftMargin = newLeftMargin; - mlp.rightMargin = newRightMargin; - } + final boolean consumesSystemWindowInsetsTop; + if (Flags.actionModeEdgeToEdge()) { + final Insets newPadding = innerInsets.getSystemWindowInsets(); + final Insets newMargin = innerInsets.getInsets( + WindowInsets.Type.navigationBars()); + + // Don't extend into navigation bar area so the width can align with status + // bar color view. + if (mlp.leftMargin != newMargin.left + || mlp.rightMargin != newMargin.right) { + mlpChanged = true; + mlp.leftMargin = newMargin.left; + mlp.rightMargin = newMargin.right; + } + + mPrimaryActionModeView.setPadding( + newPadding.left - newMargin.left, + newPadding.top, + newPadding.right - newMargin.right, + 0); + consumesSystemWindowInsetsTop = newPadding.top > 0; + } else { + int newTopMargin = innerInsets.getSystemWindowInsetTop(); + int newLeftMargin = innerInsets.getSystemWindowInsetLeft(); + int newRightMargin = innerInsets.getSystemWindowInsetRight(); + + // Must use root window insets for the guard, because the color views + // consume the navigation bar inset if the window does not request + // LAYOUT_HIDE_NAV - but the status guard is attached at the root. + WindowInsets rootInsets = getRootWindowInsets(); + int newGuardLeftMargin = rootInsets.getSystemWindowInsetLeft(); + int newGuardRightMargin = rootInsets.getSystemWindowInsetRight(); + + if (mlp.topMargin != newTopMargin || mlp.leftMargin != newLeftMargin + || mlp.rightMargin != newRightMargin) { + mlpChanged = true; + mlp.topMargin = newTopMargin; + mlp.leftMargin = newLeftMargin; + mlp.rightMargin = newRightMargin; + } - if (newTopMargin > 0 && mStatusGuard == null) { - mStatusGuard = new View(mContext); - mStatusGuard.setVisibility(GONE); - final LayoutParams lp = new LayoutParams(MATCH_PARENT, - mlp.topMargin, Gravity.LEFT | Gravity.TOP); - lp.leftMargin = newGuardLeftMargin; - lp.rightMargin = newGuardRightMargin; - addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), lp); - } else if (mStatusGuard != null) { - final LayoutParams lp = (LayoutParams) - mStatusGuard.getLayoutParams(); - if (lp.height != mlp.topMargin || lp.leftMargin != newGuardLeftMargin - || lp.rightMargin != newGuardRightMargin) { - lp.height = mlp.topMargin; + if (newTopMargin > 0 && mStatusGuard == null) { + mStatusGuard = new View(mContext); + mStatusGuard.setVisibility(GONE); + final LayoutParams lp = new LayoutParams(MATCH_PARENT, + mlp.topMargin, Gravity.LEFT | Gravity.TOP); lp.leftMargin = newGuardLeftMargin; lp.rightMargin = newGuardRightMargin; - mStatusGuard.setLayoutParams(lp); + addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), lp); + } else if (mStatusGuard != null) { + final LayoutParams lp = (LayoutParams) + mStatusGuard.getLayoutParams(); + if (lp.height != mlp.topMargin || lp.leftMargin != newGuardLeftMargin + || lp.rightMargin != newGuardRightMargin) { + lp.height = mlp.topMargin; + lp.leftMargin = newGuardLeftMargin; + lp.rightMargin = newGuardRightMargin; + mStatusGuard.setLayoutParams(lp); + } } - } - // The action mode's theme may differ from the app, so - // always show the status guard above it if we have one. - showStatusGuard = mStatusGuard != null; + // The action mode's theme may differ from the app, so + // always show the status guard above it if we have one. + showStatusGuard = mStatusGuard != null; - if (showStatusGuard && mStatusGuard.getVisibility() != VISIBLE) { - // If it wasn't previously shown, the color may be stale - updateStatusGuardColor(); + if (showStatusGuard && mStatusGuard.getVisibility() != VISIBLE) { + // If it wasn't previously shown, the color may be stale + updateStatusGuardColor(); + } + consumesSystemWindowInsetsTop = showStatusGuard; } // We only need to consume the insets if the action @@ -1664,14 +1690,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // screen_simple_overlay_action_mode.xml). final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate() & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0; - if (nonOverlay && showStatusGuard) { + if (nonOverlay && consumesSystemWindowInsetsTop) { insets = insets.inset(0, insets.getSystemWindowInsetTop(), 0, 0); } } else { - // reset top margin - if (mlp.topMargin != 0 || mlp.leftMargin != 0 || mlp.rightMargin != 0) { - mlpChanged = true; - mlp.topMargin = 0; + if (!Flags.actionModeEdgeToEdge()) { + // reset top margin + if (mlp.topMargin != 0 || mlp.leftMargin != 0 || mlp.rightMargin != 0) { + mlpChanged = true; + mlp.topMargin = 0; + } } } if (mlpChanged) { @@ -1679,7 +1707,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } } - if (mStatusGuard != null) { + if (!Flags.actionModeEdgeToEdge() && mStatusGuard != null) { mStatusGuard.setVisibility(showStatusGuard ? VISIBLE : GONE); } return insets; @@ -2183,7 +2211,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind for (int i = getChildCount() - 1; i >= 0; i--) { View v = getChildAt(i); if (v != mStatusColorViewState.view && v != mNavigationColorViewState.view - && v != mStatusGuard) { + && (Flags.actionModeEdgeToEdge() || v != mStatusGuard)) { removeViewAt(i); } } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 80fc218839d5..d5bb51187ba4 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -34,6 +34,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; +import com.android.window.flags.Flags; /** * @hide @@ -315,12 +316,14 @@ public class ActionBarContextView extends AbsActionBarView { final int contentWidth = MeasureSpec.getSize(widthMeasureSpec); - int maxHeight = mContentHeight > 0 ? - mContentHeight : MeasureSpec.getSize(heightMeasureSpec); + final int maxHeight = !Flags.actionModeEdgeToEdge() && mContentHeight > 0 + ? mContentHeight : MeasureSpec.getSize(heightMeasureSpec); final int verticalPadding = getPaddingTop() + getPaddingBottom(); int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight(); - final int height = maxHeight - verticalPadding; + final int height = Flags.actionModeEdgeToEdge() + ? mContentHeight > 0 ? mContentHeight : maxHeight + : maxHeight - verticalPadding; final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); if (mClose != null) { @@ -376,7 +379,8 @@ public class ActionBarContextView extends AbsActionBarView { } setMeasuredDimension(contentWidth, measuredHeight); } else { - setMeasuredDimension(contentWidth, maxHeight); + setMeasuredDimension(contentWidth, Flags.actionModeEdgeToEdge() + ? mContentHeight + verticalPadding : maxHeight); } } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index ff57fd4fe2ce..362b79db4003 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -294,54 +294,24 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar } } - private boolean applyInsets(View view, Rect insets, boolean toPadding, - boolean left, boolean top, boolean right, boolean bottom) { - boolean changed; - if (toPadding) { - changed = setMargin(view, EMPTY_RECT, left, top, right, bottom); - changed |= setPadding(view, insets, left, top, right, bottom); - } else { - changed = setPadding(view, EMPTY_RECT, left, top, right, bottom); - changed |= setMargin(view, insets, left, top, right, bottom); - } - return changed; - } - - private boolean setPadding(View view, Rect insets, - boolean left, boolean top, boolean right, boolean bottom) { - if ((left && view.getPaddingLeft() != insets.left) - || (top && view.getPaddingTop() != insets.top) - || (right && view.getPaddingRight() != insets.right) - || (bottom && view.getPaddingBottom() != insets.bottom)) { - view.setPadding( - left ? insets.left : view.getPaddingLeft(), - top ? insets.top : view.getPaddingTop(), - right ? insets.right : view.getPaddingRight(), - bottom ? insets.bottom : view.getPaddingBottom()); - return true; - } - return false; - } - - private boolean setMargin(View view, Rect insets, - boolean left, boolean top, boolean right, boolean bottom) { + private boolean setMargin(View view, int left, int top, int right, int bottom) { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); boolean changed = false; - if (left && lp.leftMargin != insets.left) { + if (lp.leftMargin != left) { changed = true; - lp.leftMargin = insets.left; + lp.leftMargin = left; } - if (top && lp.topMargin != insets.top) { + if (lp.topMargin != top) { changed = true; - lp.topMargin = insets.top; + lp.topMargin = top; } - if (right && lp.rightMargin != insets.right) { + if (lp.rightMargin != right) { changed = true; - lp.rightMargin = insets.right; + lp.rightMargin = right; } - if (bottom && lp.bottomMargin != insets.bottom) { + if (lp.bottomMargin != bottom) { changed = true; - lp.bottomMargin = insets.bottom; + lp.bottomMargin = bottom; } return changed; } @@ -367,12 +337,30 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar final Insets sysInsets = insets.getSystemWindowInsets(); mSystemInsets.set(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom); - // The top and bottom action bars are always within the content area. - boolean changed = applyInsets(mActionBarTop, mSystemInsets, - mActionBarExtendsIntoSystemInsets, true, true, true, false); - if (mActionBarBottom != null) { - changed |= applyInsets(mActionBarBottom, mSystemInsets, - mActionBarExtendsIntoSystemInsets, true, false, true, true); + boolean changed = false; + if (mActionBarExtendsIntoSystemInsets) { + // Don't extend into navigation bar area so the width can align with status bar + // color view. + final Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); + final int paddingLeft = sysInsets.left - navBarInsets.left; + final int paddingRight = sysInsets.right - navBarInsets.right; + mActionBarTop.setPadding(paddingLeft, sysInsets.top, paddingRight, 0); + changed |= setMargin( + mActionBarTop, navBarInsets.left, 0, navBarInsets.right, 0); + if (mActionBarBottom != null) { + mActionBarBottom.setPadding(paddingLeft, 0, paddingRight, sysInsets.bottom); + changed |= setMargin( + mActionBarBottom, navBarInsets.left, 0, navBarInsets.right, 0); + } + } else { + mActionBarTop.setPadding(0, 0, 0, 0); + changed |= setMargin( + mActionBarTop, sysInsets.left, sysInsets.top, sysInsets.right, 0); + if (mActionBarBottom != null) { + mActionBarBottom.setPadding(0, 0, 0, 0); + changed |= setMargin( + mActionBarTop, sysInsets.left, 0, sysInsets.right, sysInsets.bottom); + } } // Cannot use the result of computeSystemWindowInsets, because that consumes the @@ -521,7 +509,12 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar ); } } - setMargin(mContent, mContentInsets, true, true, true, true); + setMargin( + mContent, + mContentInsets.left, + mContentInsets.top, + mContentInsets.right, + mContentInsets.bottom); if (!mLastInnerInsets.equals(mInnerInsets)) { // If the inner insets have changed, we need to dispatch this down to diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9773f557dfaa..8c5b20da73ef 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2812,6 +2812,20 @@ <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer> <!-- Limit of how long the device can remain unlocked due to attention checking. --> <integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. --> + + <!-- Enables or disables the 'prevent screen timeout' feature, where when a user manually + undims the screen, the feature acquires a wakelock to prevent screen timeout. + false = disabled, true = enabled. Disabled by default. --> + <bool name="config_defaultPreventScreenTimeoutEnabled">false</bool> + <!-- Default value (in milliseconds) to prevent the screen timeout after user undims it + manually between screen dims, a sign the user is interacting with the device. --> + <integer name="config_defaultPreventScreenTimeoutForMillis">300000</integer> <!-- 5 minutes. --> + <!-- Default max duration (in milliseconds) of the time between undims to still consider them + consecutive. --> + <integer name="config_defaultMaxDurationBetweenUndimsMillis">600000</integer> <!-- 10 min. --> + <!-- Default number of user undims required to trigger preventing screen sleep. --> + <integer name="config_defaultUndimsRequired">2</integer> + <!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. --> <bool name="config_dreamsOnlyEnabledForDockUser">false</bool> <!-- Whether dreams are disabled when ambient mode is suppressed. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2b7261b62c64..c87ab283e188 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4442,6 +4442,11 @@ <!-- For Attention Service --> <java-symbol type="integer" name="config_attentionMaximumExtension" /> + <java-symbol type="bool" name="config_defaultPreventScreenTimeoutEnabled" /> + <java-symbol type="integer" name="config_defaultPreventScreenTimeoutForMillis" /> + <java-symbol type="integer" name="config_defaultMaxDurationBetweenUndimsMillis" /> + <java-symbol type="integer" name="config_defaultUndimsRequired" /> + <java-symbol type="string" name="config_incidentReportApproverPackage" /> <java-symbol type="array" name="config_restrictedImagesServices" /> diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 1e72d64397d7..ab2804626361 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -194,3 +194,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_gsf" + namespace: "multitasking" + description: "Applies GSF font styles to multitasking." + bug: "400534660" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt index 2bb6cf4ec3aa..73277310ffe4 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF +import android.util.TypedValue import android.view.View import com.android.wm.shell.shared.R @@ -37,14 +38,21 @@ class DropTargetView(context: Context) : View(context) { private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) style = Paint.Style.STROKE - strokeWidth = context.resources.getDimensionPixelSize(R.dimen.drop_target_stroke).toFloat() + strokeWidth = 1.dpToPx() } - private val cornerRadius = context.resources.getDimensionPixelSize( - R.dimen.drop_target_radius).toFloat() + private val cornerRadius = 28.dpToPx() private val rect = RectF(0f, 0f, 0f, 0f) + // TODO b/396539130: Use shared xml resources once we can access them in launcher + private fun Int.dpToPx() = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this.toFloat(), + context.resources.displayMetrics + ) + override fun onDraw(canvas: Canvas) { canvas.save() canvas.drawRoundRect(rect, cornerRadius, cornerRadius, rectPaint) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java index e7e7be9cdf6b..d5f2dbdbf5f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java @@ -159,19 +159,22 @@ public class BubbleTransitions { private final WindowContainerTransaction mPendingWct; private final boolean mReleasedOnLeft; private final float mTaskScale; + private final float mCornerRadius; private final PointF mDragPosition; /** * @param releasedOnLeft true if the bubble was released in the left drop target * @param taskScale the scale of the task when it was dragged to bubble + * @param cornerRadius the corner radius of the task when it was dragged to bubble * @param dragPosition the position of the task when it was dragged to bubble * @param wct pending operations to be applied when finishing the drag */ - public DragData(boolean releasedOnLeft, float taskScale, @Nullable PointF dragPosition, - @Nullable WindowContainerTransaction wct) { + public DragData(boolean releasedOnLeft, float taskScale, float cornerRadius, + @Nullable PointF dragPosition, @Nullable WindowContainerTransaction wct) { mPendingWct = wct; mReleasedOnLeft = releasedOnLeft; mTaskScale = taskScale; + mCornerRadius = cornerRadius; mDragPosition = dragPosition != null ? dragPosition : new PointF(0, 0); } @@ -198,6 +201,13 @@ public class BubbleTransitions { } /** + * @return the corner radius of the task when it was dragged to bubble + */ + public float getCornerRadius() { + return mCornerRadius; + } + + /** * @return position of the task when it was dragged to bubble */ public PointF getDragPosition() { @@ -362,6 +372,7 @@ public class BubbleTransitions { (int) mDragData.getDragPosition().y); startTransaction.setScale(mSnapshot, mDragData.getTaskScale(), mDragData.getTaskScale()); + startTransaction.setCornerRadius(mSnapshot, mDragData.getCornerRadius()); } // Now update state (and talk to launcher) in parallel with snapshot stuff @@ -377,12 +388,6 @@ public class BubbleTransitions { startTransaction.setPosition(mSnapshot, left, top); startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE); - BubbleBarExpandedView bbev = mBubble.getBubbleBarExpandedView(); - if (bbev != null) { - // Corners get reset during the animation. Add them back - startTransaction.setCornerRadius(mSnapshot, bbev.getRestingCornerRadius()); - } - startTransaction.apply(); mTaskViewTransitions.onExternalDone(transition); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 214e6ad455a1..aeef211ae3f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -143,6 +143,9 @@ public class PipBoundsState { */ public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect(); + private final List<OnPipComponentChangedListener> mOnPipComponentChangedListeners = + new ArrayList<>(); + // the size of the current bounds relative to the max size spec private float mBoundsScale; @@ -156,9 +159,7 @@ public class PipBoundsState { // Update the relative proportion of the bounds compared to max possible size. Max size // spec takes the aspect ratio of the bounds into account, so both width and height // scale by the same factor. - addPipExclusionBoundsChangeCallback((bounds) -> { - updateBoundsScale(); - }); + addPipExclusionBoundsChangeCallback((bounds) -> updateBoundsScale()); } /** Reloads the resources. */ @@ -341,11 +342,14 @@ public class PipBoundsState { /** Set the last {@link ComponentName} to enter PIP mode. */ public void setLastPipComponentName(@Nullable ComponentName lastPipComponentName) { final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName); + if (!changed) return; + clearReentryState(); + setHasUserResizedPip(false); + setHasUserMovedPip(false); + final ComponentName oldComponentName = mLastPipComponentName; mLastPipComponentName = lastPipComponentName; - if (changed) { - clearReentryState(); - setHasUserResizedPip(false); - setHasUserMovedPip(false); + for (OnPipComponentChangedListener listener : mOnPipComponentChangedListeners) { + listener.onPipComponentChanged(oldComponentName, mLastPipComponentName); } } @@ -616,6 +620,21 @@ public class PipBoundsState { } } + /** Adds callback to listen on component change. */ + public void addOnPipComponentChangedListener(@NonNull OnPipComponentChangedListener listener) { + if (!mOnPipComponentChangedListeners.contains(listener)) { + mOnPipComponentChangedListeners.add(listener); + } + } + + /** Removes callback to listen on component change. */ + public void removeOnPipComponentChangedListener( + @NonNull OnPipComponentChangedListener listener) { + if (mOnPipComponentChangedListeners.contains(listener)) { + mOnPipComponentChangedListeners.remove(listener); + } + } + public LauncherState getLauncherState() { return mLauncherState; } @@ -695,7 +714,7 @@ public class PipBoundsState { * Represents the state of pip to potentially restore upon reentry. */ @VisibleForTesting - public static final class PipReentryState { + static final class PipReentryState { private static final String TAG = PipReentryState.class.getSimpleName(); private final float mSnapFraction; @@ -722,6 +741,22 @@ public class PipBoundsState { } } + /** + * Listener interface for PiP component change, i.e. the app in pip mode changes + * TODO: Move this out of PipBoundsState once pip1 is deprecated. + */ + public interface OnPipComponentChangedListener { + /** + * Callback when the component in pip mode changes. + * @param oldPipComponent previous component in pip mode, + * {@code null} if this is the very first time PiP appears. + * @param newPipComponent new component that enters pip mode. + */ + void onPipComponentChanged( + @Nullable ComponentName oldPipComponent, + @NonNull ComponentName newPipComponent); + } + /** Dumps internal state. */ public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index e20a3d839def..fec1f56c76bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -1466,11 +1466,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange // Freeze the configuration size with offset to prevent app get a configuration // changed or relaunch. This is required to make sure client apps will calculate // insets properly after layout shifted. - if (mTargetYOffset == 0) { - mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this); - } else { - mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this); - } + mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this); } // Make {@link DividerView} non-interactive while IME showing in split mode. Listen to diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 423d1750de75..f959683701d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -451,7 +451,8 @@ public abstract class WMShellModule { Optional<DesktopImmersiveController> desktopImmersiveController, WindowDecorViewModel windowDecorViewModel, Optional<TaskChangeListener> taskChangeListener, - FocusTransitionObserver focusTransitionObserver) { + FocusTransitionObserver focusTransitionObserver, + Optional<DesksTransitionObserver> desksTransitionObserver) { return new FreeformTaskTransitionObserver( context, shellInit, @@ -459,7 +460,8 @@ public abstract class WMShellModule { desktopImmersiveController, windowDecorViewModel, taskChangeListener, - focusTransitionObserver); + focusTransitionObserver, + desksTransitionObserver); } @WMSingleton @@ -772,7 +774,7 @@ public abstract class WMShellModule { Optional<BubbleController> bubbleController, OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver, DesksOrganizer desksOrganizer, - DesksTransitionObserver desksTransitionObserver, + Optional<DesksTransitionObserver> desksTransitionObserver, UserProfileContexts userProfileContexts, DesktopModeCompatPolicy desktopModeCompatPolicy, DragToDisplayTransitionHandler dragToDisplayTransitionHandler, @@ -813,7 +815,7 @@ public abstract class WMShellModule { bubbleController, overviewToDesktopTransitionObserver, desksOrganizer, - desksTransitionObserver, + desksTransitionObserver.get(), userProfileContexts, desktopModeCompatPolicy, dragToDisplayTransitionHandler, @@ -874,6 +876,7 @@ public abstract class WMShellModule { Transitions transitions, @DynamicOverride DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer shellTaskOrganizer, + DesksOrganizer desksOrganizer, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context); @@ -886,6 +889,7 @@ public abstract class WMShellModule { transitions, desktopUserRepositories, shellTaskOrganizer, + desksOrganizer, maxTaskLimit <= 0 ? null : maxTaskLimit, interactionJankMonitor, context, @@ -1215,7 +1219,6 @@ public abstract class WMShellModule { Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler, Optional<BackAnimationController> backAnimationController, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, - @NonNull DesksTransitionObserver desksTransitionObserver, ShellInit shellInit) { return desktopUserRepositories.flatMap( repository -> @@ -1228,17 +1231,21 @@ public abstract class WMShellModule { desktopMixedTransitionHandler.get(), backAnimationController.get(), desktopWallpaperActivityTokenProvider, - desksTransitionObserver, shellInit))); } @WMSingleton @Provides - static DesksTransitionObserver provideDesksTransitionObserver( - @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories, + static Optional<DesksTransitionObserver> provideDesksTransitionObserver( + Context context, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, @NonNull DesksOrganizer desksOrganizer ) { - return new DesksTransitionObserver(desktopUserRepositories, desksOrganizer); + if (DesktopModeStatus.canEnterDesktopMode(context)) { + return Optional.of( + new DesksTransitionObserver(desktopUserRepositories, desksOrganizer)); + } + return Optional.empty(); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index bbb300ea42da..f64bd757de3b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -235,6 +235,10 @@ class DesktopRepository( /** Returns the default desk in the given display. */ private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId) + /** Returns whether the given desk is active in its display. */ + fun isDeskActive(deskId: Int): Boolean = + desktopData.getAllActiveDesks().any { desk -> desk.deskId == deskId } + /** Sets the given desk as the active one in the given display. */ fun setActiveDesk(displayId: Int, deskId: Int) { logD("setActiveDesk for displayId=%d and deskId=%d", displayId, deskId) @@ -485,7 +489,7 @@ class DesktopRepository( fun getExpandedTasksOrdered(displayId: Int): List<Int> = getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) } - @VisibleForTesting + /** Returns all active non-minimized tasks for [deskId] ordered from top to bottom. */ fun getExpandedTasksIdsInDeskOrdered(deskId: Int): List<Int> = getFreeformTasksIdsInDeskInZOrder(deskId).filter { !isMinimizedTask(it) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 301b79afd537..f67323bb7eb1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -48,6 +48,7 @@ import android.view.DragEvent import android.view.MotionEvent import android.view.SurfaceControl import android.view.SurfaceControl.Transaction +import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_NONE @@ -961,10 +962,13 @@ class DesktopTasksController( .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } .toBundle(), ) + val deskId = taskRepository.getDeskIdForTask(taskId) ?: getDefaultDeskId(DEFAULT_DISPLAY) startLaunchTransition( TRANSIT_OPEN, wct, taskId, + deskId = deskId, + displayId = DEFAULT_DISPLAY, remoteTransition = remoteTransition, unminimizeReason = unminimizeReason, ) @@ -982,19 +986,26 @@ class DesktopTasksController( remoteTransition: RemoteTransition? = null, unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ) { - logV("moveTaskToFront taskId=%s", taskInfo.taskId) + val deskId = + taskRepository.getDeskIdForTask(taskInfo.taskId) ?: getDefaultDeskId(taskInfo.displayId) + logV("moveTaskToFront taskId=%s deskId=%s", taskInfo.taskId, deskId) // If a task is tiled, another task should be brought to foreground with it so let // tiling controller handle the request. if (snapEventHandler.moveTaskToFrontIfTiled(taskInfo)) { return } val wct = WindowContainerTransaction() - wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true) + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desksOrganizer.reorderTaskToFront(wct, deskId, taskInfo) + } else { + wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true) + } startLaunchTransition( transitionType = TRANSIT_TO_FRONT, wct = wct, launchingTaskId = taskInfo.taskId, remoteTransition = remoteTransition, + deskId = deskId, displayId = taskInfo.displayId, unminimizeReason = unminimizeReason, ) @@ -1006,14 +1017,22 @@ class DesktopTasksController( wct: WindowContainerTransaction, launchingTaskId: Int?, remoteTransition: RemoteTransition? = null, - displayId: Int = DEFAULT_DISPLAY, + deskId: Int, + displayId: Int, unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ): IBinder { + logV( + "startLaunchTransition type=%s launchingTaskId=%d deskId=%d displayId=%d", + WindowManager.transitTypeToString(transitionType), + launchingTaskId, + deskId, + displayId, + ) // TODO: b/397619806 - Consolidate sharable logic with [handleFreeformTaskLaunch]. var launchTransaction = wct val taskIdToMinimize = addAndGetMinimizeChanges( - displayId, + deskId, launchTransaction, newTaskId = launchingTaskId, launchingNewIntent = launchingTaskId == null, @@ -1027,21 +1046,20 @@ class DesktopTasksController( ) var activationRunOnTransitStart: RunOnTransitStart? = null val shouldActivateDesk = - (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue || - DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) && - !isDesktopModeShowing(displayId) + when { + DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue -> + !taskRepository.isDeskActive(deskId) + DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue -> { + !isDesktopModeShowing(displayId) + } + else -> false + } if (shouldActivateDesk) { - val deskIdToActivate = - checkNotNull( - launchingTaskId?.let { taskRepository.getDeskIdForTask(it) } - ?: getDefaultDeskId(displayId) - ) val activateDeskWct = WindowContainerTransaction() // TODO: b/391485148 - pass in the launching task here to apply task-limit policy, // but make sure to not do it twice since it is also done at the start of this // function. - activationRunOnTransitStart = - addDeskActivationChanges(deskIdToActivate, activateDeskWct) + activationRunOnTransitStart = addDeskActivationChanges(deskId, activateDeskWct) // Desk activation must be handled before app launch-related transactions. activateDeskWct.merge(launchTransaction, /* transfer= */ true) launchTransaction = activateDeskWct @@ -1152,7 +1170,14 @@ class DesktopTasksController( } wct.sendPendingIntent(pendingIntent, intent, ops.toBundle()) - startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + val deskId = getDefaultDeskId(displayId) + startLaunchTransition( + TRANSIT_OPEN, + wct, + launchingTaskId = null, + deskId = deskId, + displayId = displayId, + ) } /** @@ -2141,10 +2166,14 @@ class DesktopTasksController( WINDOWING_MODE_FREEFORM -> { val wct = WindowContainerTransaction() wct.sendPendingIntent(launchIntent, fillIn, options.toBundle()) + val deskId = + taskRepository.getDeskIdForTask(callingTaskInfo.taskId) + ?: getDefaultDeskId(callingTaskInfo.displayId) startLaunchTransition( transitionType = TRANSIT_OPEN, wct = wct, launchingTaskId = null, + deskId = deskId, displayId = callingTaskInfo.displayId, ) } @@ -2224,6 +2253,7 @@ class DesktopTasksController( logV("skip keyguard is locked") return null } + val deskId = getDefaultDeskId(task.displayId) val wct = WindowContainerTransaction() if (shouldFreeformTaskLaunchSwitchToFullscreen(task)) { logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId) @@ -2246,7 +2276,6 @@ class DesktopTasksController( runOnTransitStart?.invoke(transition) return wct } - val deskId = getDefaultDeskId(task.displayId) val runOnTransitStart = addDeskActivationChanges(deskId, wct, task) runOnTransitStart?.invoke(transition) wct.reorder(task.token, true) @@ -2288,7 +2317,7 @@ class DesktopTasksController( reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) // 2) minimize a Task if needed. - val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) + val taskIdToMinimize = addAndGetMinimizeChanges(deskId, wct, task.taskId) addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) if (taskIdToMinimize != null) { addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT) @@ -2334,7 +2363,7 @@ class DesktopTasksController( // The desk was already showing and we're launching a new Task - we // might need to minimize another Task. val taskIdToMinimize = - addAndGetMinimizeChanges(task.displayId, wct, task.taskId) + addAndGetMinimizeChanges(deskId, wct, task.taskId) taskIdToMinimize?.let { minimizingTaskId -> addPendingMinimizeTransition( transition, @@ -2670,7 +2699,7 @@ class DesktopTasksController( /** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */ private fun addAndGetMinimizeChanges( - displayId: Int, + deskId: Int, wct: WindowContainerTransaction, newTaskId: Int?, launchingNewIntent: Boolean = false, @@ -2679,7 +2708,7 @@ class DesktopTasksController( require(newTaskId == null || !launchingNewIntent) return desktopTasksLimiter .get() - .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent) + .addAndGetMinimizeTaskChanges(deskId, wct, newTaskId, launchingNewIntent) } private fun addPendingMinimizeTransition( @@ -2782,9 +2811,20 @@ class DesktopTasksController( taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding(displayId) ) - // TODO: b/362720497 - activating a desk with the intention to move a new task to - // it means we may need to minimize something in the activating desk. Do so here - // similar to how it's done in #bringDesktopAppsToFront. + val expandedTasksOrderedFrontToBack = + taskRepository.getExpandedTasksIdsInDeskOrdered(deskId = deskId) + // If we're adding a new Task we might need to minimize an old one + val taskIdToMinimize = + desktopTasksLimiter + .getOrNull() + ?.getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront) + if (taskIdToMinimize != null) { + val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) + // TODO(b/365725441): Handle non running task minimization + if (taskToMinimize != null) { + desksOrganizer.minimizeTask(wct, deskId, taskToMinimize) + } + } return { transition -> val activateDeskTransition = if (newTaskIdInFront != null) { @@ -2802,6 +2842,9 @@ class DesktopTasksController( ) } desksTransitionObserver.addPendingTransition(activateDeskTransition) + taskIdToMinimize?.let { minimizingTask -> + addPendingMinimizeTransition(transition, minimizingTask, MinimizeReason.TASK_LIMIT) + } } } @@ -3381,7 +3424,14 @@ class DesktopTasksController( if (windowingMode == WINDOWING_MODE_FREEFORM) { if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) { // TODO b/376389593: Use a custom tab tearing transition/animation - startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + val deskId = getDefaultDeskId(DEFAULT_DISPLAY) + startLaunchTransition( + TRANSIT_OPEN, + wct, + launchingTaskId = null, + deskId = deskId, + displayId = DEFAULT_DISPLAY, + ) } else { desktopModeDragAndDropTransitionHandler.handleDropEvent(wct) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index da369f094405..4ca58823b52b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -22,6 +22,7 @@ import android.os.Handler import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_BACK +import android.window.DesktopExperienceFlags import android.window.DesktopModeFlags import android.window.TransitionInfo import android.window.WindowContainerTransaction @@ -31,6 +32,7 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.sysui.UserChangeListener @@ -48,6 +50,7 @@ class DesktopTasksLimiter( transitions: Transitions, private val desktopUserRepositories: DesktopUserRepositories, private val shellTaskOrganizer: ShellTaskOrganizer, + private val desksOrganizer: DesksOrganizer, private val maxTasksLimit: Int?, private val interactionJankMonitor: InteractionJankMonitor, private val context: Context, @@ -258,7 +261,7 @@ class DesktopTasksLimiter( * returning the task to minimize. */ fun addAndGetMinimizeTaskChanges( - displayId: Int, + deskId: Int, wct: WindowContainerTransaction, newFrontTaskId: Int?, launchingNewIntent: Boolean = false, @@ -267,15 +270,19 @@ class DesktopTasksLimiter( val taskRepository = desktopUserRepositories.current val taskIdToMinimize = getTaskIdToMinimize( - taskRepository.getExpandedTasksOrdered(displayId), + taskRepository.getExpandedTasksIdsInDeskOrdered(deskId), newFrontTaskId, launchingNewIntent, ) - // If it's a running task, reorder it to back. taskIdToMinimize ?.let { shellTaskOrganizer.getRunningTaskInfo(it) } - // TODO: b/391485148 - this won't really work with multi-desks enabled. - ?.let { wct.reorder(it.token, /* onTop= */ false) } + ?.let { task -> + if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + wct.reorder(task.token, /* onTop= */ false) + } else { + desksOrganizer.minimizeTask(wct, deskId, task) + } + } return taskIdToMinimize } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index bd9c30e2a495..7dabeb7c9d15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -36,7 +36,6 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.back.BackAnimationController import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider -import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.desktopmode.DesktopModeStatus @@ -58,7 +57,6 @@ class DesktopTasksTransitionObserver( private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler, private val backAnimationController: BackAnimationController, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, - private val desksTransitionObserver: DesksTransitionObserver, shellInit: ShellInit, ) : Transitions.TransitionObserver { @@ -88,7 +86,6 @@ class DesktopTasksTransitionObserver( finishTransaction: SurfaceControl.Transaction, ) { // TODO: b/332682201 Update repository state - desksTransitionObserver.onTransitionReady(transition, info) if ( DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index e943c42dcdfc..4f511a901756 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -327,8 +327,9 @@ sealed class DragToDesktopTransitionHandler( val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo") val dragPosition = PointF(state.dragAnimator.position) val scale = state.dragAnimator.scale + val cornerRadius = state.dragAnimator.cornerRadius state.dragAnimator.cancelAnimator() - requestBubble(wct, taskInfo, onLeft, scale, dragPosition) + requestBubble(wct, taskInfo, onLeft, scale, cornerRadius, dragPosition) } private fun requestBubble( @@ -336,13 +337,14 @@ sealed class DragToDesktopTransitionHandler( taskInfo: RunningTaskInfo, onLeft: Boolean, taskScale: Float = 1f, + cornerRadius: Float = 0f, dragPosition: PointF = PointF(0f, 0f), ) { val controller = bubbleController.orElseThrow { IllegalStateException("BubbleController not set") } controller.expandStackAndSelectBubble( taskInfo, - BubbleTransitions.DragData(onLeft, taskScale, dragPosition, wct), + BubbleTransitions.DragData(onLeft, taskScale, cornerRadius, dragPosition, wct), ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt index fc359d7d67b6..8c4bc2598dff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt @@ -40,6 +40,13 @@ interface DesksOrganizer { task: ActivityManager.RunningTaskInfo, ) + /** Reorders a desk's task to the front. */ + fun reorderTaskToFront( + wct: WindowContainerTransaction, + deskId: Int, + task: ActivityManager.RunningTaskInfo, + ) + /** Minimizes the given task of the given deskId. */ fun minimizeTask( wct: WindowContainerTransaction, @@ -47,6 +54,13 @@ interface DesksOrganizer { task: ActivityManager.RunningTaskInfo, ) + /** Unminimize the given task of the given desk. */ + fun unminimizeTask( + wct: WindowContainerTransaction, + deskId: Int, + task: ActivityManager.RunningTaskInfo, + ) + /** Whether the change is for the given desk id. */ fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt index f576258ebdaa..2130474ff603 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt @@ -110,7 +110,31 @@ class RootTaskDesksOrganizer( wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true) } + override fun reorderTaskToFront( + wct: WindowContainerTransaction, + deskId: Int, + task: RunningTaskInfo, + ) { + logV("reorderTaskToFront task=${task.taskId} desk=$deskId") + val root = deskRootsByDeskId[deskId] ?: error("Root not found for desk: $deskId") + if (task.taskId in root.children) { + wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true) + return + } + val minimizationRoot = + checkNotNull(deskMinimizationRootsByDeskId[deskId]) { + "Minimization root not found for desk: $deskId" + } + if (task.taskId in minimizationRoot.children) { + unminimizeTask(wct, deskId, task) + wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true) + return + } + logE("Attempted to reorder task=${task.taskId} in desk=$deskId but it was not a child") + } + override fun minimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo) { + logV("minimizeTask task=${task.taskId} desk=$deskId") val deskRoot = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } val minimizationRoot = @@ -129,6 +153,30 @@ class RootTaskDesksOrganizer( wct.reparent(task.token, minimizationRoot.token, /* onTop= */ true) } + override fun unminimizeTask( + wct: WindowContainerTransaction, + deskId: Int, + task: RunningTaskInfo, + ) { + val taskId = task.taskId + logV("unminimizeTask task=$taskId desk=$deskId") + val deskRoot = + checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } + val minimizationRoot = + checkNotNull(deskMinimizationRootsByDeskId[deskId]) { + "Minimization root not found for desk: $deskId" + } + if (taskId in deskRoot.children) { + logV("Task #$taskId is already unminimized in desk=$deskId") + return + } + if (taskId !in minimizationRoot.children) { + logE("Attempted to unminimize task=$taskId in desk=$deskId but it was not a child") + return + } + wct.reparent(task.token, deskRoot.token, /* onTop= */ true) + } + override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean = (isDeskRootChange(change) && change.taskId == deskId) || (getDeskMinimizationRootInChange(change)?.deskId == deskId) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java index 8059b94685ba..f89ba0a168d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -29,6 +29,7 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.desktopmode.DesktopImmersiveController; +import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; @@ -52,6 +53,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs private final WindowDecorViewModel mWindowDecorViewModel; private final Optional<TaskChangeListener> mTaskChangeListener; private final FocusTransitionObserver mFocusTransitionObserver; + private final Optional<DesksTransitionObserver> mDesksTransitionObserver; private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo = new HashMap<>(); @@ -63,12 +65,14 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs Optional<DesktopImmersiveController> desktopImmersiveController, WindowDecorViewModel windowDecorViewModel, Optional<TaskChangeListener> taskChangeListener, - FocusTransitionObserver focusTransitionObserver) { + FocusTransitionObserver focusTransitionObserver, + Optional<DesksTransitionObserver> desksTransitionObserver) { mTransitions = transitions; mDesktopImmersiveController = desktopImmersiveController; mWindowDecorViewModel = windowDecorViewModel; mTaskChangeListener = taskChangeListener; mFocusTransitionObserver = focusTransitionObserver; + mDesksTransitionObserver = desksTransitionObserver; if (FreeformComponents.requiresFreeformComponents(context)) { shellInit.addInitCallback(this::onInit, this); } @@ -85,6 +89,10 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT) { + // Update desk state first, otherwise [TaskChangeListener] may update desktop task state + // under an outdated active desk if a desk switch and a task update happen in the same + // transition, such as when unminimizing a task from an inactive desk. + mDesksTransitionObserver.ifPresent(o -> o.onTransitionReady(transition, info)); if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) { // TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository // is updated from there **before** the |mWindowDecorViewModel| methods are invoked. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 65099c2dfb9d..671eae3d84ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -153,7 +153,12 @@ public class PhonePipMenuController implements PipMenuController, mPipUiEventLogger = pipUiEventLogger; mPipTransitionState.addPipTransitionStateChangedListener(this); - + // Clear actions after exit PiP. Otherwise, next PiP could accidentally inherit the + // actions provided by the previous app in PiP mode. + mPipBoundsState.addOnPipComponentChangedListener(((oldPipComponent, newPipComponent) -> { + if (mAppActions != null) mAppActions.clear(); + mCloseAction = null; + })); mPipTaskListener.addParamsChangedListener(new PipTaskListener.PipParamsChangedCallback() { @Override public void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index d6634845ee21..294ef48c01d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -61,7 +61,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, private final PipBoundsState mPipBoundsState; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final ShellExecutor mMainExecutor; - private final PictureInPictureParams mPictureInPictureParams = + private PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build(); private boolean mWaitingForAspectRatioChange = false; @@ -92,6 +92,11 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, } mPipResizeAnimatorSupplier = PipResizeAnimator::new; mPipScheduler.setPipParamsSupplier(this::getPictureInPictureParams); + // Reset {@link #mPictureInPictureParams} after exiting PiP. For instance, next Activity + // with null aspect ratio would accidentally inherit the aspect ratio from a previous + // PiP Activity. + mPipBoundsState.addOnPipComponentChangedListener(((oldPipComponent, newPipComponent) -> + mPictureInPictureParams = new PictureInPictureParams.Builder().build())); } void setPictureInPictureParams(@Nullable PictureInPictureParams params) { @@ -138,9 +143,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.hasSetAspectRatio() && mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(newAspectRatio) && PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) { - mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { - onAspectRatioChanged(newAspectRatio); - }); + mPipTransitionState.setOnIdlePipTransitionStateRunnable( + () -> onAspectRatioChanged(newAspectRatio)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 7472b0ea56ca..c370c0cb0930 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2920,7 +2920,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50); - } else if (isSplitScreenVisible() && isOpening) { + } else if (enableFlexibleTwoAppSplit() && isSplitScreenVisible() && isOpening) { // launching into an existing split stage; possibly launchAdjacent // If we're replacing a pip-able app, we need to let mixed handler take care of // it. Otherwise we'll just treat it as an enter+resize diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt index 22bc9782170b..cf536eba8382 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt @@ -42,8 +42,6 @@ class MoveToDesktopAnimator @JvmOverloads constructor( .setDuration(ANIMATION_DURATION.toLong()) .apply { val t = SurfaceControl.Transaction() - val cornerRadius = context.resources - .getDimensionPixelSize(R.dimen.desktop_mode_dragged_task_radius).toFloat() addUpdateListener { setTaskPosition(mostRecentInput.x, mostRecentInput.y) t.setScale(taskSurface, scale, scale) @@ -57,6 +55,8 @@ class MoveToDesktopAnimator @JvmOverloads constructor( val taskId get() = taskInfo.taskId val position: PointF = PointF(0.0f, 0.0f) + val cornerRadius: Float = context.resources + .getDimensionPixelSize(R.dimen.desktop_mode_dragged_task_radius).toFloat() /** * Whether motion events from the drag gesture should affect the dragged surface or not. Used diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index c3f5b3f20f4a..30712b55bdfa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -466,11 +466,7 @@ class AppHeaderViewHolder( override fun onHandleMenuOpened() {} - override fun onHandleMenuClosed() { - openMenuButton.post { - openMenuButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) - } - } + override fun onHandleMenuClosed() {} fun onMaximizeWindowHoverExit() { maximizeButtonView.cancelHoverAnimation() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java index 3c79ea7be39f..925ca0f1638d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java @@ -221,8 +221,8 @@ public class BubbleTransitionsTest extends ShellTestCase { PointF dragPosition = new PointF(10f, 20f); BubbleTransitions.DragData dragData = new BubbleTransitions.DragData( - /* releasedOnLeft= */ false, /* taskScale= */ 0.5f, dragPosition, - pendingWct); + /* releasedOnLeft= */ false, /* taskScale= */ 0.5f, /* cornerRadius= */ 10f, + dragPosition, pendingWct); final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble( mBubble, taskInfo, mExpandedViewManager, mTaskViewFactory, mBubblePositioner, @@ -253,6 +253,8 @@ public class BubbleTransitionsTest extends ShellTestCase { verify(startT).setPosition(snapshot, dragPosition.x, dragPosition.y); // Snapshot has the scale of the dragged task verify(startT).setScale(snapshot, dragData.getTaskScale(), dragData.getTaskScale()); + // Snapshot has dragged task corner radius + verify(startT).setCornerRadius(snapshot, dragData.getCornerRadius()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java index 01b76edd9b25..1066276becc7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java @@ -19,6 +19,8 @@ package com.android.wm.shell.common.pip; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -128,6 +130,31 @@ public class PipBoundsStateTest extends ShellTestCase { } @Test + public void setLastPipComponentName_notChanged_doesNotCallbackComponentChangedListener() { + mPipBoundsState.setLastPipComponentName(mTestComponentName1); + PipBoundsState.OnPipComponentChangedListener mockListener = + mock(PipBoundsState.OnPipComponentChangedListener.class); + + mPipBoundsState.addOnPipComponentChangedListener(mockListener); + mPipBoundsState.setLastPipComponentName(mTestComponentName1); + + verify(mockListener, never()).onPipComponentChanged(any(), any()); + } + + @Test + public void setLastPipComponentName_changed_callbackComponentChangedListener() { + mPipBoundsState.setLastPipComponentName(mTestComponentName1); + PipBoundsState.OnPipComponentChangedListener mockListener = + mock(PipBoundsState.OnPipComponentChangedListener.class); + + mPipBoundsState.addOnPipComponentChangedListener(mockListener); + mPipBoundsState.setLastPipComponentName(mTestComponentName2); + + verify(mockListener).onPipComponentChanged( + eq(mTestComponentName1), eq(mTestComponentName2)); + } + + @Test public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() { mPipBoundsState.setLastPipComponentName(mTestComponentName1); mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 8f499c959759..87b7d344a3ec 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -323,6 +323,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, MAX_TASK_LIMIT, mockInteractionJankMonitor, mContext, @@ -2363,6 +2364,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveTaskToFront_postsWctWithReorderOp() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -2385,9 +2387,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_reordersToFront() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(task1.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(task1, remoteTransition = null) + + verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(task1)) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_bringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() { setUpHomeTask() - val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + } whenever( desktopMixedTransitionHandler.startLaunchTransition( eq(TRANSIT_TO_FRONT), @@ -2408,6 +2435,32 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_bringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() { + val deskId = 0 + setUpHomeTask() + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(freeformTasks[0].taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(freeformTasks[0], remoteTransition = null) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[1]) + } + + @Test fun moveTaskToFront_minimizedTask_marksTaskAsUnminimized() { val transition = Binder() val freeformTask = setUpFreeformTask() @@ -2496,8 +2549,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } val task = createRecentTaskInfo(1001) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) whenever( @@ -2520,6 +2578,33 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + val task = createRecentTaskInfo(1001) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + eq(task.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test fun moveToNextDisplay_noOtherDisplays() { whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -3530,7 +3615,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksDisabled_dontMinimize() { val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) val fullscreenTask = createFullscreenTask() @@ -3543,7 +3629,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksEnabled_dontMinimize() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTask = + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also { + markTaskVisible(it) + } + + // Launch a fullscreen task while in the desk. + val fullscreenTask = createFullscreenTask() + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(deskId), any()) + assertNull(desktopTasksLimiter.getMinimizingTask(transition)) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() { val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } freeformTasks.forEach { markTaskVisible(it) } val fullscreenTask = createFullscreenTask() @@ -3557,7 +3665,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also { + markTaskVisible(it) + } + } + + // Launch a fullscreen task while in the desk. + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + val minimizingTask = + assertNotNull(desktopTasksLimiter.getMinimizingTask(transition)?.taskId) + assertThat(minimizingTask).isEqualTo(freeformTasks[0].taskId) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() { val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } freeformTasks.forEach { markTaskVisible(it) } val fullscreenTask = createFullscreenTask() @@ -3578,7 +3713,31 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + freeformTasks.forEach { markTaskVisible(it) } + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct) + // The launching task is moved to the desk. + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + // The bottom-most task is minimized. + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksDisabled_existingAndNewTasksAreMinimized() { val minimizedTask = setUpFreeformTask() taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId) val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } @@ -3604,6 +3763,88 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksEnabled_existingAndNewTasksAreMinimized() { + // A desk with a minimized tasks, and non-minimized tasks already at the task limit. + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val minimizedTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.minimizeTaskInDesk( + displayId = DEFAULT_DISPLAY, + deskId = deskId, + taskId = minimizedTask.taskId, + ) + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also { + markTaskVisible(it) + } + } + + // Launch a fullscreen task that brings Home to front with it. + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_taskAddedToDesk() { + // A desk with a minimized tasks, and non-minimized tasks already at the task limit. + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + + // Launch a fullscreen task that brings Home to front with it. + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + ) + fun handleRequest_fullscreenTaskWithTaskOnHome_activatesDesk() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + + // Launch a fullscreen task that brings Home to front with it. + val homeTask = setUpHomeTask() + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(fullscreenTask)) + + assertNotNull(wct) + wct.assertReorder(homeTask, toTop = true) + wct.assertReorder(wallpaperToken, toTop = true) + verify(desksOrganizer).activateDesk(wct, deskId) + verify(desksTransitionsObserver) + .addPendingTransition( + DeskTransition.ActiveDeskWithTask( + token = transition, + displayId = DEFAULT_DISPLAY, + deskId = deskId, + enterTaskId = fullscreenTask.taskId, + ) + ) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() { whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) @@ -3663,8 +3904,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksDisabled_minimize() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } freeformTasks.forEach { markTaskVisible(it) } val newFreeformTask = createFreeformTask() @@ -3677,6 +3923,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksEnabled_minimize() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + freeformTasks.forEach { markTaskVisible(it) } + val newFreeformTask = createFreeformTask() + + val wct = + controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN)) + + assertNotNull(wct) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() { val deskId = 0 val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) @@ -5578,7 +5842,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFreeformAddsNewWindow() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun openInstance_fromFreeformAddsNewWindow_multiDesksDisabled() { setUpLandscapeDisplay() val task = setUpFreeformTask() val taskToRequest = setUpFreeformTask() @@ -5592,7 +5857,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) ) .thenReturn(Binder()) + runOpenInstance(task, taskToRequest.taskId) + verify(desktopMixedTransitionHandler) .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull()) val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) @@ -5601,10 +5868,42 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun openInstance_fromFreeformAddsNewWindow_multiDesksEnabled() { + setUpLandscapeDisplay() + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + val taskToRequest = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(taskToRequest.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + runOpenInstance(task, taskToRequest.taskId) + + verify(desktopMixedTransitionHandler) + .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull()) + verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(taskToRequest)) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFreeform_minimizesIfNeeded() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun openInstance_fromFreeform_multiDesksDisabled_minimizesIfNeeded() { setUpLandscapeDisplay() - val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } val oldestTask = freeformTasks.first() val newestTask = freeformTasks.last() @@ -5630,6 +5929,40 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun openInstance_fromFreeform_multiDesksEnabled_minimizesIfNeeded() { + setUpLandscapeDisplay() + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + val oldestTask = freeformTasks.first() + val newestTask = freeformTasks.last() + + val transition = Binder() + val wctCaptor = argumentCaptor<WindowContainerTransaction>() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + anyInt(), + wctCaptor.capture(), + anyInt(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + runOpenInstance(newestTask, freeformTasks[1].taskId) + + val wct = wctCaptor.firstValue + verify(desksOrganizer).minimizeTask(wct, deskId, oldestTask) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) fun openInstance_fromFreeform_exitsImmersiveIfNeeded() { setUpLandscapeDisplay() @@ -6587,7 +6920,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, ) fun startLaunchTransition_desktopNotShowing_movesWallpaperToFront() { - val launchingTask = createFreeformTask() + val launchingTask = createFreeformTask(displayId = DEFAULT_DISPLAY) val wct = WindowContainerTransaction() wct.reorder(launchingTask.token, /* onTop= */ true) whenever( @@ -6601,7 +6934,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) .thenReturn(Binder()) - controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + controller.startLaunchTransition( + transitionType = TRANSIT_OPEN, + wct = wct, + launchingTaskId = null, + deskId = 0, + displayId = DEFAULT_DISPLAY, + ) val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) val launchingTaskReorderIndex = latestWct.indexOfReorder(launchingTask, toTop = true) @@ -6625,6 +6964,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() transitionType = TRANSIT_OPEN, wct = WindowContainerTransaction(), launchingTaskId = null, + deskId = 0, + displayId = DEFAULT_DISPLAY, ) verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(any()) @@ -6634,6 +6975,51 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun startLaunchTransition_launchingTaskFromInactiveDesk_otherDeskActive_activatesDesk() { + val activeDeskId = 4 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId) + val inactiveDesk = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk) + val launchingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk) + val transition = Binder() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + eq(launchingTask.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + val wct = WindowContainerTransaction() + controller.startLaunchTransition( + transitionType = TRANSIT_OPEN, + wct = wct, + launchingTaskId = launchingTask.taskId, + deskId = inactiveDesk, + displayId = DEFAULT_DISPLAY, + ) + + verify(desksOrganizer).activateDesk(any(), eq(inactiveDesk)) + verify(desksTransitionsObserver) + .addPendingTransition( + DeskTransition.ActivateDesk( + token = transition, + displayId = DEFAULT_DISPLAY, + deskId = inactiveDesk, + ) + ) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, ) fun startLaunchTransition_desktopShowing_doesNotReorderWallpaper() { val wct = WindowContainerTransaction() @@ -6648,8 +7034,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) .thenReturn(Binder()) - setUpFreeformTask() - controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + setUpFreeformTask(deskId = 0, displayId = DEFAULT_DISPLAY) + controller.startLaunchTransition( + transitionType = TRANSIT_OPEN, + wct = wct, + launchingTaskId = null, + deskId = 0, + displayId = DEFAULT_DISPLAY, + ) val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) assertNull(latestWct.hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 76103640c029..eeecb00b5b08 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -39,12 +39,14 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION +import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.shared.desktopmode.DesktopModeStatus @@ -67,10 +69,13 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.spy +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.never import org.mockito.quality.Strictness /** @@ -84,6 +89,7 @@ import org.mockito.quality.Strictness class DesktopTasksLimiterTest : ShellTestCase() { @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer + @Mock lateinit var desksOrganizer: DesksOrganizer @Mock lateinit var transitions: Transitions @Mock lateinit var interactionJankMonitor: InteractionJankMonitor @Mock lateinit var handler: Handler @@ -128,6 +134,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, MAX_TASK_LIMIT, interactionJankMonitor, mContext, @@ -148,6 +155,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, 0, interactionJankMonitor, mContext, @@ -163,6 +171,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, -5, interactionJankMonitor, mContext, @@ -178,6 +187,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, maxTasksLimit = null, interactionJankMonitor, mContext, @@ -394,7 +404,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun addAndGetMinimizeTaskChanges_tasksWithinLimit_noTaskMinimized() { + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksDisabled_noTaskMinimized() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } @@ -402,7 +413,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = DEFAULT_DISPLAY, + deskId = 0, wct = wct, newFrontTaskId = setUpFreeformTask().taskId, ) @@ -412,7 +423,27 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun addAndGetMinimizeTaskChanges_tasksAboveLimit_backTaskMinimized() { + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksEnabled_noTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + deskId = 0, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) + + assertThat(minimizedTaskId).isNull() + verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any()) + } + + @Test + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksDisabled_backTaskMinimized() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) // The following list will be ordered bottom -> top, as the last task is moved to top last. @@ -421,7 +452,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = DEFAULT_DISPLAY, + deskId = DEFAULT_DISPLAY, wct = wct, newFrontTaskId = setUpFreeformTask().taskId, ) @@ -433,7 +464,28 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_noTaskMinimized() { + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksEnabled_backTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + // The following list will be ordered bottom -> top, as the last task is moved to top last. + val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + deskId = DEFAULT_DISPLAY, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) + + assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId) + verify(desksOrganizer).minimizeTask(wct, deskId = 0, tasks.first()) + } + + @Test + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksDisabled_noTaskMinimized() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } @@ -442,7 +494,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = 0, + deskId = 0, wct = wct, newFrontTaskId = setUpFreeformTask().taskId, ) @@ -452,6 +504,26 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksEnabled_noTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId) + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + deskId = 0, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) + + assertThat(minimizedTaskId).isNull() + verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any()) + } + + @Test fun getTaskToMinimize_tasksWithinLimit_returnsNull() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) @@ -485,6 +557,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, MAX_TASK_LIMIT2, interactionJankMonitor, mContext, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index fd8842b6d99b..a7dc706eb6c9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -48,13 +48,11 @@ import com.android.wm.shell.back.BackAnimationController import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider -import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP -import com.android.wm.shell.util.StubTransaction import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Before @@ -95,7 +93,6 @@ class DesktopTasksTransitionObserverTest { private val backAnimationController = mock<BackAnimationController>() private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() - private val desksTransitionObserver = mock<DesksTransitionObserver>() private val wallpaperToken = MockToken().token() private lateinit var transitionObserver: DesktopTasksTransitionObserver @@ -119,7 +116,6 @@ class DesktopTasksTransitionObserverTest { mixedHandler, backAnimationController, desktopWallpaperActivityTokenProvider, - desksTransitionObserver, shellInit, ) } @@ -403,21 +399,6 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false) } - @Test - fun onTransitionReady_forwardsToDesksTransitionObserver() { - val transition = Binder() - val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0) - - transitionObserver.onTransitionReady( - transition = transition, - info = info, - StubTransaction(), - StubTransaction(), - ) - - verify(desksTransitionObserver).onTransitionReady(transition, info) - } - private fun createBackNavigationTransition( task: RunningTaskInfo?, type: Int = TRANSIT_TO_BACK, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt index 96b85ad2729e..891a1f1685cd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt @@ -21,9 +21,11 @@ import android.view.Display import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.TransitionInfo +import android.window.WindowContainerToken import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.Change import android.window.WindowContainerTransaction.HierarchyOp +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTaskOrganizer @@ -473,14 +475,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task) - assertThat( - wct.hierarchyOps.any { hop -> - hop.isReparent && - hop.container == task.token.asBinder() && - hop.newParent == desk.minimizationRoot.token.asBinder() - } - ) - .isTrue() + assertThat(wct.hasMinimizationHops(desk, task.token)).isTrue() } @Test @@ -508,6 +503,110 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { assertThat(wct.isEmpty).isTrue() } + @Test + fun unminimizeTask() { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task) + organizer.onTaskAppeared(task, SurfaceControl()) + organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task) + task.parentTaskId = desk.minimizationRoot.rootId + organizer.onTaskInfoChanged(task) + + wct.clear() + organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isTrue() + } + + @Test + fun unminimizeTask_alreadyUnminimized_noOp() { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task) + organizer.onTaskAppeared(task, SurfaceControl()) + + wct.clear() + organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isFalse() + } + + @Test + fun unminimizeTask_notInDesk_noOp() { + val desk = createDesk() + val task = createFreeformTask() + val wct = WindowContainerTransaction() + + organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isFalse() + } + + @Test + fun reorderTaskToFront() { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.onTaskAppeared(task, SurfaceControl()) + + organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task) + + assertThat( + wct.hierarchyOps.singleOrNull { hop -> + hop.container == task.token.asBinder() && + hop.type == HIERARCHY_OP_TYPE_REORDER && + hop.toTop && + hop.includingParents() + } + ) + .isNotNull() + } + + @Test + fun reorderTaskToFront_notInDesk_noOp() { + val desk = createDesk() + val task = createFreeformTask() + val wct = WindowContainerTransaction() + + organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task) + + assertThat( + wct.hierarchyOps.singleOrNull { hop -> + hop.container == task.token.asBinder() && + hop.type == HIERARCHY_OP_TYPE_REORDER && + hop.toTop && + hop.includingParents() + } + ) + .isNull() + } + + @Test + fun reorderTaskToFront_minimized_unminimizesAndReorders() { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.onTaskAppeared(task, SurfaceControl()) + task.parentTaskId = desk.minimizationRoot.rootId + organizer.onTaskInfoChanged(task) + + organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isTrue() + assertThat( + wct.hierarchyOps.singleOrNull { hop -> + hop.container == task.token.asBinder() && + hop.type == HIERARCHY_OP_TYPE_REORDER && + hop.toTop && + hop.includingParents() + } + ) + .isNotNull() + } + private data class DeskRoots( val deskRoot: DeskRoot, val minimizationRoot: DeskMinimizationRoot, @@ -525,6 +624,27 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { ) } + private fun WindowContainerTransaction.hasMinimizationHops( + desk: DeskRoots, + task: WindowContainerToken, + ): Boolean = + hierarchyOps.any { hop -> + hop.isReparent && + hop.container == task.asBinder() && + hop.newParent == desk.minimizationRoot.token.asBinder() + } + + private fun WindowContainerTransaction.hasUnminimizationHops( + desk: DeskRoots, + task: WindowContainerToken, + ): Boolean = + hierarchyOps.any { hop -> + hop.isReparent && + hop.container == task.asBinder() && + hop.newParent == desk.deskRoot.token.asBinder() && + hop.toTop + } + private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback { var deskId: Int? = null val created: Boolean diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java index bc918450a3cf..714e5f486285 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java @@ -44,10 +44,12 @@ import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.desktopmode.DesktopImmersiveController; +import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.StubTransaction; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; @@ -68,6 +70,7 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase { @Mock private WindowDecorViewModel mWindowDecorViewModel; @Mock private TaskChangeListener mTaskChangeListener; @Mock private FocusTransitionObserver mFocusTransitionObserver; + @Mock private DesksTransitionObserver mDesksTransitionObserver; private FreeformTaskTransitionObserver mTransitionObserver; @@ -88,7 +91,8 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase { Optional.of(mDesktopImmersiveController), mWindowDecorViewModel, Optional.of(mTaskChangeListener), - mFocusTransitionObserver); + mFocusTransitionObserver, + Optional.of(mDesksTransitionObserver)); final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), same(mTransitionObserver)); @@ -357,6 +361,18 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase { verify(mDesktopImmersiveController).onTransitionFinished(transition, /* aborted= */ false); } + @Test + public void onTransitionReady_forwardsToDesksTransitionObserver() { + final IBinder transition = mock(IBinder.class); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, /* flags= */ 0) + .build(); + + mTransitionObserver.onTransitionReady(transition, info, new StubTransaction(), + new StubTransaction()); + + verify(mDesksTransitionObserver).onTransitionReady(transition, info); + } + private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) { final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = taskId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java index 333569a7206e..5029371c3419 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.kotlin.MatchersKt.eq; import static org.mockito.kotlin.VerificationKt.clearInvocations; @@ -35,7 +36,9 @@ import android.app.ActivityManager; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Context; +import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -48,8 +51,10 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.pip2.animation.PipResizeAnimator; import org.junit.Before; @@ -107,6 +112,16 @@ public class PipTaskListenerTest { } @Test + public void constructor_addOnPipComponentChangedListener() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + + verify(mMockPipBoundsState).addOnPipComponentChangedListener( + any(PipBoundsState.OnPipComponentChangedListener.class)); + } + + @Test public void setPictureInPictureParams_updatePictureInPictureParams() { mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, @@ -359,6 +374,26 @@ public class PipTaskListenerTest { verify(mMockPipResizeAnimator, times(0)).start(); } + @Test + public void onPipComponentChanged_clearPictureInPictureParams() { + when(mMockContext.getResources()).thenReturn(mock(Resources.class)); + PipBoundsState pipBoundsState = new PipBoundsState(mMockContext, + mock(PhoneSizeSpecSource.class), mock(PipDisplayLayoutState.class)); + pipBoundsState.setLastPipComponentName(new ComponentName("org.test", "test1")); + + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, pipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + pipBoundsState.setLastPipComponentName(new ComponentName("org.test", "test2")); + + assertTrue(mPipTaskListener.getPictureInPictureParams().empty()); + } + private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio, String... actions) { final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 30594dcfa939..0d45149267cf 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1265,6 +1265,9 @@ struct ResTable_config // Varies in length from 3 to 8 chars. Zero-filled value. char localeNumberingSystem[8]; + // Mark all padding explicitly so it's clear how much we can expand it. + char endPadding[3]; + void copyFromDeviceNoSwap(const ResTable_config& o) { const auto o_size = dtohl(o.size); if (o_size >= sizeof(ResTable_config)) [[likely]] { @@ -1422,6 +1425,13 @@ struct ResTable_config void swapHtoD_slow(); }; +// Fix the struct size for backward compatibility +static_assert(sizeof(ResTable_config) == 64); + +// Make sure there's no unaccounted padding in the structure. +static_assert(offsetof(ResTable_config, endPadding) + + sizeof(ResTable_config::endPadding) == sizeof(ResTable_config)); + /** * A specification of the resources defined by a particular type. * diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index a210ddf54b2e..7d227f793817 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -722,10 +722,15 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() { auto canvas = SkCanvas(recycledPixels->getSkBitmap()); SkRect destination = SkRect::Make(recycledPixels->info().bounds()); - destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds())); - canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination, - SkSamplingOptions(SkFilterMode::kLinear), nullptr, - SkCanvas::kFast_SrcRectConstraint); + if (destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds()))) { + canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination, + SkSamplingOptions(SkFilterMode::kLinear), nullptr, + SkCanvas::kFast_SrcRectConstraint); + } else { + // The canvas would have discarded the draw operation automatically, but + // this case should have been detected before getting to this point. + ALOGE("Copy destination does not intersect image bounds"); + } } else { void* dst = recycledPixels->pixels(); const size_t dstRowBytes = mRecycledBitmap->rowBytes(); diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index dafcc729b8f1..d929b0de391a 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -187,6 +187,9 @@ <!-- Default state of tap to wake --> <bool name="def_double_tap_to_wake">true</bool> + <!-- Default setting for double tap to sleep (Settings.Secure.DOUBLE_TAP_TO_SLEEP) --> + <bool name="def_double_tap_to_sleep">false</bool> + <!-- Default for Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT --> <string name="def_nfc_payment_component"></string> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index c0105298899b..f0a0483aa17c 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -85,6 +85,7 @@ public class SecureSettings { Settings.Secure.MOUNT_UMS_PROMPT, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED, Settings.Secure.DOUBLE_TAP_TO_WAKE, + Settings.Secure.DOUBLE_TAP_TO_SLEEP, Settings.Secure.WAKE_GESTURE_ENABLED, Settings.Secure.LONG_PRESS_TIMEOUT, Settings.Secure.KEY_REPEAT_ENABLED, @@ -293,5 +294,7 @@ public class SecureSettings { Settings.Secure.FINGERPRINT_APP_ENABLED, Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED, Settings.Secure.DUAL_SHADE, + Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED, + Settings.Secure.SEARCH_CONTENT_FILTERS_ENABLED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 0ffdf53f2036..b5de7e719043 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -131,6 +131,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.MOUNT_UMS_PROMPT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.MOUNT_UMS_NOTIFY_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DOUBLE_TAP_TO_WAKE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.DOUBLE_TAP_TO_SLEEP, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.WAKE_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LONG_PRESS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.KEY_REPEAT_ENABLED, BOOLEAN_VALIDATOR); @@ -461,5 +462,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.FINGERPRINT_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DUAL_SHADE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.BROWSER_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.SEARCH_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 4a225bdbd7e5..65ede9d804d0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -4080,7 +4080,7 @@ public class SettingsProvider extends ContentProvider { @VisibleForTesting final class UpgradeController { - private static final int SETTINGS_VERSION = 227; + private static final int SETTINGS_VERSION = 228; private final int mUserId; @@ -6319,6 +6319,23 @@ public class SettingsProvider extends ContentProvider { currentVersion = 227; } + // Version 227: Add default value for DOUBLE_TAP_TO_SLEEP. + if (currentVersion == 227) { + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting doubleTapToSleep = secureSettings.getSettingLocked( + Settings.Secure.DOUBLE_TAP_TO_SLEEP); + if (doubleTapToSleep.isNull()) { + secureSettings.insertSettingOverrideableByRestoreLocked( + Settings.Secure.DOUBLE_TAP_TO_SLEEP, + getContext().getResources().getBoolean( + R.bool.def_double_tap_to_sleep) ? "1" : "0", + null /* tag */, + true /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } + currentVersion = 228; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt index 96feeedb8793..e734dd26eb15 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt @@ -25,6 +25,7 @@ data class AxisDefinition( ) object GSFAxes { + @JvmStatic val WEIGHT = AxisDefinition( tag = "wght", diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 5b073e49192a..4a39cff388a9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -39,6 +39,7 @@ interface TypefaceVariantCache { fun getTypefaceForVariant(fvar: String?): Typeface? companion object { + @JvmStatic fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface { if (fVar.isNullOrEmpty()) { return baseTypeface diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt index 0bd51cd9822d..44f2353dcb75 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt @@ -111,8 +111,8 @@ private constructor(metadata: FailureMetadata, private val actual: TransitionSta } companion object { - fun transitionStates() = Factory { metadata, actual: TransitionState -> - TransitionStateSubject(metadata, actual) + fun transitionStates() = Factory { metadata, actual: TransitionState? -> + TransitionStateSubject(metadata, actual!!) } } } @@ -181,8 +181,8 @@ private constructor(metadata: FailureMetadata, actual: TransitionState.Transitio companion object { fun sceneTransitions() = - Factory { metadata, actual: TransitionState.Transition.ChangeScene -> - SceneTransitionSubject(metadata, actual) + Factory { metadata, actual: TransitionState.Transition.ChangeScene? -> + SceneTransitionSubject(metadata, actual!!) } } } @@ -202,8 +202,8 @@ private constructor( companion object { fun showOrHideOverlayTransitions() = - Factory { metadata, actual: TransitionState.Transition.ShowOrHideOverlay -> - ShowOrHideOverlayTransitionSubject(metadata, actual) + Factory { metadata, actual: TransitionState.Transition.ShowOrHideOverlay? -> + ShowOrHideOverlayTransitionSubject(metadata, actual!!) } } } @@ -221,8 +221,8 @@ private constructor(metadata: FailureMetadata, actual: TransitionState.Transitio companion object { fun replaceOverlayTransitions() = - Factory { metadata, actual: TransitionState.Transition.ReplaceOverlay -> - ReplaceOverlayTransitionSubject(metadata, actual) + Factory { metadata, actual: TransitionState.Transition.ReplaceOverlay? -> + ReplaceOverlayTransitionSubject(metadata, actual!!) } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt index ab31038fac8f..368a333fbd78 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt @@ -49,8 +49,8 @@ class DpOffsetSubject(metadata: FailureMetadata, private val actual: DpOffset) : val DefaultTolerance = Dp(.5f) fun dpOffsets() = - Factory<DpOffsetSubject, DpOffset> { metadata, actual -> - DpOffsetSubject(metadata, actual) + Factory { metadata, actual: DpOffset? -> + DpOffsetSubject(metadata, actual!!) } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt index e9e61a718f08..37acbe261f76 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt @@ -161,15 +161,7 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) : } override fun onThemeChanged(theme: ThemeConfig) { - val color = - when { - theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> - clockCtx.resources.getColor(android.R.color.system_accent1_100) - else -> clockCtx.resources.getColor(android.R.color.system_accent2_600) - } - - view.updateColor(color) + view.updateColor(theme.getDefaultColor(clockCtx.context)) } override fun onFontSettingChanged(fontSizePx: Float) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 365567b17ec0..bc4bdf4243cb 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -149,14 +149,7 @@ class DefaultClockController( override fun onThemeChanged(theme: ThemeConfig) { this@DefaultClockFaceController.theme = theme - val color = - when { - theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> - resources.getColor(android.R.color.system_accent1_100) - else -> resources.getColor(android.R.color.system_accent2_600) - } - + val color = theme.getDefaultColor(ctx) if (currentColor == color) { return } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt index 97004ef6f9a9..1d963af3ad22 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -229,15 +229,7 @@ open class SimpleDigitalHandLayerController( } override fun onThemeChanged(theme: ThemeConfig) { - val color = - when { - theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> - clockCtx.resources.getColor(android.R.color.system_accent1_100) - else -> clockCtx.resources.getColor(android.R.color.system_accent2_600) - } - - view.updateColor(color) + view.updateColor(theme.getDefaultColor(clockCtx.context)) refreshTime() } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index fae17a5321ff..0ec2d188833a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -45,6 +45,7 @@ import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.replace import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.toFVar import com.android.systemui.plugins.clocks.ClockLogger +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.shared.clocks.CanvasUtil.translate import com.android.systemui.shared.clocks.CanvasUtil.use import com.android.systemui.shared.clocks.ClockContext @@ -330,7 +331,7 @@ open class SimpleDigitalClockTextView( textAnimator.setTextStyle( TextAnimator.Style( fVar = if (isDozing) aodFontVariation else lsFontVariation, - color = if (isDozing) AOD_COLOR else lockscreenColor, + color = if (isDozing && !ambientAod()) AOD_COLOR else lockscreenColor, textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize, ), TextAnimator.Animation( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt index e0515000b232..454c15667f22 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.phone.dozeScrimController import com.android.systemui.statusbar.phone.screenOffAnimationController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy @@ -105,7 +106,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun nonPowerButtonFPS_vibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) runCurrent() enterDeviceFromFingerprintUnlockLegacy() @@ -116,7 +117,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun powerButtonFPS_vibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) @@ -133,7 +134,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun powerButtonFPS_powerDown_doNotVibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN @@ -150,7 +151,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) @@ -174,14 +175,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { } @Test - fun nonPowerButtonFPS_coExFaceFailure_vibrateError() = + fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) enrollFace() runCurrent() faceFailure() - assertThat(playErrorHaptic).isNotNull() + assertThat(playErrorHaptic).isNull() } @Test @@ -211,7 +212,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) runCurrent() configureDeviceEntryFromBiometricSource(isFpUnlock = true) @@ -225,7 +226,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) @@ -246,18 +247,19 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { enrollFace() kosmos.configureKeyguardBypass(isBypassAvailable = true) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) configureDeviceEntryFromBiometricSource(isFaceUnlock = true) verifyDeviceEntryFromFaceAuth() assertThat(playSuccessHaptic).isNotNull() } + @OptIn(ExperimentalCoroutinesApi::class) @EnableSceneContainer @Test - fun playSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() = + fun skipSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() = testScope.runTest { underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFace() kosmos.configureKeyguardBypass(isBypassAvailable = false) @@ -265,7 +267,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { configureDeviceEntryFromBiometricSource(isFaceUnlock = true, bypassEnabled = false) kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true - assertThat(playSuccessHaptic).isNotNull() + assertThat(playSuccessHaptic).isNull() } @EnableSceneContainer @@ -274,7 +276,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) // power button is currently DOWN kosmos.fakeKeyEventRepository.setPowerButtonDown(true) @@ -295,7 +297,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt new file mode 100644 index 000000000000..d5d256e5cd97 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2025 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.log + +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.model.Statement +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +@SmallTest +class LogWtfHandlerRuleTest : SysuiTestCase() { + + val underTest = LogWtfHandlerRule() + + @Test + fun passingTestWithoutWtf_shouldPass() { + val result = runTestCodeWithRule { + Log.e(TAG, "just an error", IndexOutOfBoundsException()) + } + assertThat(result.isSuccess).isTrue() + } + + @Test + fun passingTestWithWtf_shouldFail() { + val result = runTestCodeWithRule { + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + } + assertThat(result.isFailure).isTrue() + val exception = result.exceptionOrNull() + assertThat(exception).isInstanceOf(AssertionError::class.java) + assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + @Test + fun failingTestWithoutWtf_shouldFail() { + val result = runTestCodeWithRule { + Log.e(TAG, "just an error", IndexOutOfBoundsException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + } + + @Test + fun failingTestWithWtf_shouldFail() { + val result = runTestCodeWithRule { + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions + assertThat(suppressedExceptions).hasSize(1) + val suppressed = suppressedExceptions.first() + assertThat(suppressed).isInstanceOf(AssertionError::class.java) + assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + @Test + fun passingTestWithExemptWtf_shouldPass() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + } + assertThat(result.isSuccess).isTrue() + } + + @Test + fun failingTestWithExemptWtf_shouldFail() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions + assertThat(suppressedExceptions).isEmpty() + } + + @Test + fun passingTestWithOneExemptWtfOfTwo_shouldFail() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + } + assertThat(result.isFailure).isTrue() + val exception = result.exceptionOrNull() + assertThat(exception).isInstanceOf(AssertionError::class.java) + assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + @Test + fun failingTestWithOneExemptWtfOfTwo_shouldFail() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions + assertThat(suppressedExceptions).hasSize(1) + val suppressed = suppressedExceptions.first() + assertThat(suppressed).isInstanceOf(AssertionError::class.java) + assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + private fun runTestCodeWithRule(testCode: () -> Unit): Result<Unit> { + val testCodeStatement = + object : Statement() { + override fun evaluate() { + testCode() + } + } + val wrappedTest = underTest.apply(testCodeStatement, mock()) + return try { + wrappedTest.evaluate() + Result.success(Unit) + } catch (e: Throwable) { + Result.failure(e) + } + } + + companion object { + const val TAG = "LogWtfHandlerRuleTest" + const val TAG_EXPECTED = "EXPECTED" + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 9adf24f32c0c..1743e056b65c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -863,7 +863,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasUdfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -885,7 +885,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasUdfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -907,7 +907,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -930,7 +930,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1033,7 +1033,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1056,7 +1056,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1079,7 +1079,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1102,7 +1102,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1160,7 +1160,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun playsFaceErrorHaptics_nonSfps_coEx() = + fun skipsFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1172,15 +1172,14 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper).vibrateAuthError(anyString()) + assertThat(playErrorHaptic).isNull() + verify(vibratorHelper, never()).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun playsMSDLFaceErrorHaptics_nonSfps_coEx() = + fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1192,10 +1191,9 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) - assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + assertThat(playErrorHaptic).isNull() + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 3407cd50e76f..4a304071ee97 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -41,7 +41,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.flag.junit.FlagsParameterization; -import android.provider.Settings; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.view.WindowManager; @@ -71,7 +70,6 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; -import com.android.systemui.util.settings.FakeSettings; import com.google.common.util.concurrent.MoreExecutors; @@ -113,7 +111,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; - private FakeSettings mSecureSettings; private final Executor mMainExecutor = MoreExecutors.directExecutor(); private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); @@ -135,9 +132,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mSecureSettings = new FakeSettings(); - mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 0); - // Preferred refresh rate is equal to the first displayMode's refresh rate mPreferredRefreshRate = mContext.getDisplay().getSystemSupportedModes()[0].getRefreshRate(); overrideResource( @@ -171,7 +165,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { () -> mSelectedUserInteractor, mUserTracker, mKosmos.getNotificationShadeWindowModel(), - mSecureSettings, mKosmos::getCommunalInteractor, mKosmos.getShadeLayoutParams()); mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {}); @@ -355,19 +348,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { } @Test - public void setKeyguardShowingWithSecureWindowsDisabled_disablesSecureFlag() { - mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 1); - mNotificationShadeWindowController.setBouncerShowing(true); - - verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); - assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue(); - assertThat( - (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY) - != 0) - .isTrue(); - } - - @Test public void setKeyguardNotShowing_disablesSecureFlag() { mNotificationShadeWindowController.setBouncerShowing(false); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index fad66581682f..0642467a001b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.shared.clocks import android.content.res.Resources import android.graphics.Color import android.graphics.drawable.Drawable +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.util.TypedValue import android.view.LayoutInflater import android.widget.FrameLayout @@ -29,6 +31,7 @@ import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.ClockId import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.ThemeConfig +import com.android.systemui.shared.Flags import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -103,6 +106,26 @@ class DefaultClockProviderTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_AMBIENT_AOD) + fun defaultClock_initialize_flagOff() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) + verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) + + clock.initialize(true, 0f, 0f, null) + + // This is the default darkTheme color + val expectedColor = context.resources.getColor(android.R.color.system_accent1_100) + verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor) + verify(mockLargeClockView).setColors(DOZE_COLOR, expectedColor) + verify(mockSmallClockView).onTimeZoneChanged(notNull()) + verify(mockLargeClockView).onTimeZoneChanged(notNull()) + verify(mockSmallClockView).refreshTime() + verify(mockLargeClockView).refreshTime() + } + + @Test + @EnableFlags(Flags.FLAG_AMBIENT_AOD) fun defaultClock_initialize() { val clock = provider.createClock(DEFAULT_CLOCK_ID) verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) @@ -110,7 +133,7 @@ class DefaultClockProviderTest : SysuiTestCase() { clock.initialize(true, 0f, 0f, null) - val expectedColor = 0 + val expectedColor = Color.MAGENTA verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor) verify(mockLargeClockView).setColors(DOZE_COLOR, expectedColor) verify(mockSmallClockView).onTimeZoneChanged(notNull()) @@ -165,8 +188,10 @@ class DefaultClockProviderTest : SysuiTestCase() { } @Test - fun defaultClock_events_onThemeChanged_noSeed() { - val expectedColor = 0 + @DisableFlags(Flags.FLAG_AMBIENT_AOD) + fun defaultClock_events_onThemeChanged_noSeed_flagOff() { + // This is the default darkTheme color + val expectedColor = context.resources.getColor(android.R.color.system_accent1_100) val clock = provider.createClock(DEFAULT_CLOCK_ID) verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) @@ -180,6 +205,22 @@ class DefaultClockProviderTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_AMBIENT_AOD) + fun defaultClock_events_onThemeChanged_noSeedn() { + val expectedColor = Color.TRANSPARENT + val clock = provider.createClock(DEFAULT_CLOCK_ID) + + verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) + verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) + + clock.smallClock.events.onThemeChanged(ThemeConfig(true, null)) + clock.largeClock.events.onThemeChanged(ThemeConfig(true, null)) + + verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) + verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) + } + + @Test fun defaultClock_events_onThemeChanged_newSeed() { val initSeedColor = 10 val newSeedColor = 20 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index 96c4a59f752d..670ebadcf5a7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -62,6 +62,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.addNotif import com.android.systemui.statusbar.notification.data.repository.addNotifs +import com.android.systemui.statusbar.notification.data.repository.removeNotif import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.phone.SystemUIDialog @@ -349,6 +350,36 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) } + @EnableChipsModernization + @Test + fun chips_threeChips_isSmallPortrait_allSquished() = + kosmos.runTest { + screenRecordState.value = ScreenRecordModel.Recording + addOngoingCallState(key = "call") + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.shortCriticalText = "Some text here" + } + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = promotedContentBuilder.build(), + ) + ) + + val latest by collectLastValue(underTest.chips) + + // Squished chips are icon only + assertThat(latest!!.active[0]) + .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat(latest!!.active[1]) + .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat(latest!!.active[2]) + .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + } + @DisableChipsModernization @Test fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() = @@ -851,7 +882,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @EnableChipsModernization @Test - fun chips_threePromotedNotifs_topTwoActiveThirdInOverflow() = + fun chips_fourPromotedNotifs_topThreeActiveFourthInOverflow() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val unused by collectLastValue(underTest.chipsLegacy) @@ -859,6 +890,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val firstIcon = createStatusBarIconViewOrNull() val secondIcon = createStatusBarIconViewOrNull() val thirdIcon = createStatusBarIconViewOrNull() + val fourthIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -879,20 +911,27 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { promotedContent = PromotedNotificationContentModel.Builder("thirdNotif").build(), ), + activeNotificationModel( + key = "fourthNotif", + statusBarChipIcon = thirdIcon, + promotedContent = + PromotedNotificationContentModel.Builder("fourthNotif").build(), + ), ) ) - assertThat(latest!!.active.size).isEqualTo(2) + assertThat(latest!!.active.size).isEqualTo(3) assertIsNotifChip(latest!!.active[0], context, firstIcon, "firstNotif") assertIsNotifChip(latest!!.active[1], context, secondIcon, "secondNotif") + assertIsNotifChip(latest!!.active[2], context, thirdIcon, "thirdNotif") assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, thirdIcon, "thirdNotif") + assertIsNotifChip(latest!!.overflow[0], context, fourthIcon, "fourthNotif") assertThat(latest!!.inactive.size).isEqualTo(4) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) } @Test - fun visibleChipKeys_threePromotedNotifs_topTwoInList() = + fun visibleChipKeys_fourPromotedNotifs_topThreeInList() = kosmos.runTest { val latest by collectLastValue(underTest.visibleChipKeys) @@ -916,10 +955,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { promotedContent = PromotedNotificationContentModel.Builder("thirdNotif").build(), ), + activeNotificationModel( + key = "fourthNotif", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = + PromotedNotificationContentModel.Builder("fourthNotif").build(), + ), ) ) - assertThat(latest).containsExactly("firstNotif", "secondNotif").inOrder() + assertThat(latest).containsExactly("firstNotif", "secondNotif", "thirdNotif").inOrder() } @DisableChipsModernization @@ -957,7 +1002,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @EnableChipsModernization @Test - fun chips_callAndPromotedNotifs_callAndFirstNotifActiveSecondNotifInOverflow() = + fun chips_callAndPromotedNotifs_callAndFirstTwoNotifsActive_thirdNotifInOverflow() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val unused by collectLastValue(underTest.chipsLegacy) @@ -965,6 +1010,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val callNotificationKey = "call" val firstIcon = createStatusBarIconViewOrNull() val secondIcon = createStatusBarIconViewOrNull() + val thirdIcon = createStatusBarIconViewOrNull() addOngoingCallState(key = callNotificationKey) activeNotificationListRepository.addNotifs( listOf( @@ -980,21 +1026,28 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { promotedContent = PromotedNotificationContentModel.Builder("secondNotif").build(), ), + activeNotificationModel( + key = "thirdNotif", + statusBarChipIcon = thirdIcon, + promotedContent = + PromotedNotificationContentModel.Builder("thirdNotif").build(), + ), ) ) - assertThat(latest!!.active.size).isEqualTo(2) + assertThat(latest!!.active.size).isEqualTo(3) assertIsCallChip(latest!!.active[0], callNotificationKey, context) assertIsNotifChip(latest!!.active[1], context, firstIcon, "firstNotif") + assertIsNotifChip(latest!!.active[2], context, secondIcon, "secondNotif") assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, secondIcon, "secondNotif") + assertIsNotifChip(latest!!.overflow[0], context, thirdIcon, "thirdNotif") assertThat(latest!!.inactive.size).isEqualTo(3) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) } @DisableChipsModernization @Test - fun chipsLegacy_screenRecordAndCallAndPromotedNotifs_notifsNotShown() = + fun chipsLegacy_screenRecordAndCallAndPromotedNotif_notifNotShown() = kosmos.runTest { val callNotificationKey = "call" val latest by collectLastValue(underTest.chipsLegacy) @@ -1016,7 +1069,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topTwoInList() = + fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topThreeInList() = kosmos.runTest { val latest by collectLastValue(underTest.visibleChipKeys) @@ -1025,20 +1078,27 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.Recording activeNotificationListRepository.addNotif( activeNotificationModel( - key = "notif", + key = "notif1", statusBarChipIcon = createStatusBarIconViewOrNull(), - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + promotedContent = PromotedNotificationContentModel.Builder("notif1").build(), + ) + ) + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif2", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = PromotedNotificationContentModel.Builder("notif2").build(), ) ) assertThat(latest) - .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey) + .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey, "notif1") .inOrder() } @EnableChipsModernization @Test - fun chips_screenRecordAndCallAndPromotedNotif_notifInOverflow() = + fun chips_screenRecordAndCallAndPromotedNotifs_secondNotifInOverflow() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val unused by collectLastValue(underTest.chipsLegacy) @@ -1055,11 +1115,22 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) addOngoingCallState(key = callNotificationKey) - assertThat(latest!!.active.size).isEqualTo(2) + // This is the overflow notif + val notifIcon2 = createStatusBarIconViewOrNull() + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif2", + statusBarChipIcon = notifIcon2, + promotedContent = PromotedNotificationContentModel.Builder("notif2").build(), + ) + ) + + assertThat(latest!!.active.size).isEqualTo(3) assertIsScreenRecordChip(latest!!.active[0]) assertIsCallChip(latest!!.active[1], callNotificationKey, context) + assertIsNotifChip(latest!!.active[2], context, notifIcon, "notif") assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.overflow[0], context, notifIcon2, "notif2") assertThat(latest!!.inactive.size).isEqualTo(2) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) } @@ -1234,15 +1305,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @Test fun chips_movesChipsAroundAccordingToPriority() = kosmos.runTest { + systemClock.setCurrentTimeMillis(10_000) val callNotificationKey = "call" // Start with just the lowest priority chip active - val notifIcon = createStatusBarIconViewOrNull() + val notif1Icon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( - key = "notif", - statusBarChipIcon = notifIcon, - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + key = "notif1", + statusBarChipIcon = notif1Icon, + promotedContent = PromotedNotificationContentModel.Builder("notif1").build(), ) ) ) @@ -1254,7 +1326,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val unused by collectLastValue(underTest.chipsLegacy) assertThat(latest!!.active.size).isEqualTo(1) - assertIsNotifChip(latest!!.active[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[0], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(4) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) @@ -1262,10 +1334,10 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // WHEN the higher priority call chip is added addOngoingCallState(key = callNotificationKey) - // THEN the higher priority call chip and notif are active in that order + // THEN the higher priority call chip and notif1 are active in that order assertThat(latest!!.active.size).isEqualTo(2) assertIsCallChip(latest!!.active[0], callNotificationKey, context) - assertIsNotifChip(latest!!.active[1], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[1], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(3) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) @@ -1278,56 +1350,63 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - // THEN the higher priority media projection chip and call are active in that order, and - // notif is demoted to overflow - assertThat(latest!!.active.size).isEqualTo(2) + // THEN media projection, then call, then notif1 are active + assertThat(latest!!.active.size).isEqualTo(3) assertIsShareToAppChip(latest!!.active[0]) assertIsCallChip(latest!!.active[1], callNotificationKey, context) - assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[2], context, notif1Icon, "notif1") + assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(2) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) - // WHEN the higher priority screen record chip is added + // WHEN the screen record chip is added, which replaces media projection screenRecordState.value = ScreenRecordModel.Recording + // AND another notification is added + systemClock.advanceTime(2_000) + val notif2Icon = createStatusBarIconViewOrNull() + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif2", + statusBarChipIcon = notif2Icon, + promotedContent = PromotedNotificationContentModel.Builder("notif2").build(), + ) + ) - // THEN the higher priority screen record chip and call are active in that order, and - // media projection and notif are demoted in overflow - assertThat(latest!!.active.size).isEqualTo(2) + // THEN screen record, then call, then notif2 are active + assertThat(latest!!.active.size).isEqualTo(3) assertIsScreenRecordChip(latest!!.active[0]) assertIsCallChip(latest!!.active[1], callNotificationKey, context) + assertIsNotifChip(latest!!.active[2], context, notif2Icon, "notif2") + + // AND notif1 and media projection is demoted in overflow assertThat(latest!!.overflow.size).isEqualTo(2) assertIsShareToAppChip(latest!!.overflow[0]) - assertIsNotifChip(latest!!.overflow[1], context, notifIcon, "notif") + assertIsNotifChip(latest!!.overflow[1], context, notif1Icon, "notif1") assertThat(latest!!.inactive.size).isEqualTo(1) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) - // WHEN screen record and call is dropped + // WHEN screen record and call are dropped screenRecordState.value = ScreenRecordModel.DoingNothing - setNotifs( - listOf( - activeNotificationModel( - key = "notif", - statusBarChipIcon = notifIcon, - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), - ) - ) - ) + removeOngoingCallState(callNotificationKey) - // THEN media projection and notif remain - assertThat(latest!!.active.size).isEqualTo(2) + // THEN media projection, notif2, and notif1 remain + assertThat(latest!!.active.size).isEqualTo(3) assertIsShareToAppChip(latest!!.active[0]) - assertIsNotifChip(latest!!.active[1], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[1], context, notif2Icon, "notif2") + assertIsNotifChip(latest!!.active[2], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(3) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) // WHEN media projection is dropped mediaProjectionState.value = MediaProjectionState.NotProjecting + // AND notif2 is dropped + systemClock.advanceTime(2_000) + activeNotificationListRepository.removeNotif("notif2") - // THEN only notif is active + // THEN only notif1 is active assertThat(latest!!.active.size).isEqualTo(1) - assertIsNotifChip(latest!!.active[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[0], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(4) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt new file mode 100644 index 000000000000..e4b2e6c9b359 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import android.app.Notification +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper +import android.view.MotionEvent +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.stub +import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import org.junit.Before + +private fun getCloseButton(row: ExpandableNotificationRow): View { + val contractedView = row.showingLayout?.contractedChild!! + return contractedView.findViewById(com.android.internal.R.id.close_button) +} + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationCloseButtonTest : SysuiTestCase() { + private lateinit var helper: NotificationTestHelper + + @Before + fun setUp() { + helper = NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this) + ) + } + + @Test + @DisableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS) + fun verifyWhenFeatureDisabled() { + // Enable the notification row to dismiss. + helper.dismissibilityProvider.stub { + on { isDismissable(any()) } doReturn true + } + + // By default, the close button should be gone. + val row = createNotificationRow() + val closeButton = getCloseButton(row) + assertThat(closeButton).isNotNull() + assertThat(closeButton.visibility).isEqualTo(View.GONE) + + val hoverEnterEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_ENTER, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // The close button should not show if the feature is disabled. + row.onInterceptHoverEvent(hoverEnterEvent) + assertThat(closeButton.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS) + fun verifyOnDismissableNotification() { + // Enable the notification row to dismiss. + helper.dismissibilityProvider.stub { + on { isDismissable(any()) } doReturn true + } + + // By default, the close button should be gone. + val row = createNotificationRow() + val closeButton = getCloseButton(row) + assertThat(closeButton).isNotNull() + assertThat(closeButton.visibility).isEqualTo(View.GONE) + + val hoverEnterEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_ENTER, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // When the row is hovered, the close button should show. + row.onInterceptHoverEvent(hoverEnterEvent) + assertThat(closeButton.visibility).isEqualTo(View.VISIBLE) + + val hoverExitEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_EXIT, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // When hover exits the row, the close button should be gone again. + row.onInterceptHoverEvent(hoverExitEvent) + assertThat(closeButton.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS) + fun verifyOnUndismissableNotification() { + // By default, the close button should be gone. + val row = createNotificationRow() + val closeButton = getCloseButton(row) + assertThat(closeButton).isNotNull() + assertThat(closeButton.visibility).isEqualTo(View.GONE) + + val hoverEnterEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_ENTER, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // Because the host notification cannot be dismissed, the close button should not show. + row.onInterceptHoverEvent(hoverEnterEvent) + assertThat(closeButton.visibility).isEqualTo(View.GONE) + } + + private fun createNotificationRow(): ExpandableNotificationRow { + val notification = Notification.Builder(context, "channel") + .setContentTitle("title") + .setContentText("text") + .setSmallIcon(R.drawable.ic_person) + .build() + + return helper.createRow(notification) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt index 029e54658f60..20ee6c120ee8 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt @@ -13,6 +13,7 @@ */ package com.android.systemui.plugins.clocks +import android.content.Context import android.graphics.Rect import com.android.systemui.plugins.annotations.ProtectedInterface @@ -60,4 +61,12 @@ data class ThemeConfig( * value denotes that we should use the seed color for the current system theme. */ val seedColor: Int?, -) +) { + fun getDefaultColor(context: Context): Int { + return when { + seedColor != null -> seedColor!! + isDarkTheme -> context.resources.getColor(android.R.color.system_accent1_100) + else -> context.resources.getColor(android.R.color.system_accent2_600) + } + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt index f920b187e7e5..f59dda049aa1 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt @@ -24,6 +24,8 @@ data class WeatherData( @VisibleForTesting const val TEMPERATURE_KEY = "temperature" private const val INVALID_WEATHER_ICON_STATE = -1 + @JvmStatic + @JvmOverloads fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? { val description = extras.getString(DESCRIPTION_KEY) val state = @@ -46,7 +48,7 @@ data class WeatherData( state = state, useCelsius = extras.getBoolean(USE_CELSIUS_KEY), temperature = temperature, - touchAction = touchAction + touchAction = touchAction, ) if (DEBUG) { Log.i(TAG, "Weather data parsed $result from $extras") @@ -87,53 +89,53 @@ data class WeatherData( } // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon - enum class WeatherStateIcon(val id: Int) { - UNKNOWN_ICON(0), + enum class WeatherStateIcon(val id: Int, val icon: String) { + UNKNOWN_ICON(0, ""), // Clear, day & night. - SUNNY(1), - CLEAR_NIGHT(2), + SUNNY(1, "a"), + CLEAR_NIGHT(2, "f"), // Mostly clear, day & night. - MOSTLY_SUNNY(3), - MOSTLY_CLEAR_NIGHT(4), + MOSTLY_SUNNY(3, "b"), + MOSTLY_CLEAR_NIGHT(4, "n"), // Partly cloudy, day & night. - PARTLY_CLOUDY(5), - PARTLY_CLOUDY_NIGHT(6), + PARTLY_CLOUDY(5, "b"), + PARTLY_CLOUDY_NIGHT(6, "n"), // Mostly cloudy, day & night. - MOSTLY_CLOUDY_DAY(7), - MOSTLY_CLOUDY_NIGHT(8), - CLOUDY(9), - HAZE_FOG_DUST_SMOKE(10), - DRIZZLE(11), - HEAVY_RAIN(12), - SHOWERS_RAIN(13), + MOSTLY_CLOUDY_DAY(7, "e"), + MOSTLY_CLOUDY_NIGHT(8, "e"), + CLOUDY(9, "e"), + HAZE_FOG_DUST_SMOKE(10, "d"), + DRIZZLE(11, "c"), + HEAVY_RAIN(12, "c"), + SHOWERS_RAIN(13, "c"), // Scattered showers, day & night. - SCATTERED_SHOWERS_DAY(14), - SCATTERED_SHOWERS_NIGHT(15), + SCATTERED_SHOWERS_DAY(14, "c"), + SCATTERED_SHOWERS_NIGHT(15, "c"), // Isolated scattered thunderstorms, day & night. - ISOLATED_SCATTERED_TSTORMS_DAY(16), - ISOLATED_SCATTERED_TSTORMS_NIGHT(17), - STRONG_TSTORMS(18), - BLIZZARD(19), - BLOWING_SNOW(20), - FLURRIES(21), - HEAVY_SNOW(22), + ISOLATED_SCATTERED_TSTORMS_DAY(16, "i"), + ISOLATED_SCATTERED_TSTORMS_NIGHT(17, "i"), + STRONG_TSTORMS(18, "i"), + BLIZZARD(19, "j"), + BLOWING_SNOW(20, "j"), + FLURRIES(21, "h"), + HEAVY_SNOW(22, "j"), // Scattered snow showers, day & night. - SCATTERED_SNOW_SHOWERS_DAY(23), - SCATTERED_SNOW_SHOWERS_NIGHT(24), - SNOW_SHOWERS_SNOW(25), - MIXED_RAIN_HAIL_RAIN_SLEET(26), - SLEET_HAIL(27), - TORNADO(28), - TROPICAL_STORM_HURRICANE(29), - WINDY_BREEZY(30), - WINTRY_MIX_RAIN_SNOW(31); + SCATTERED_SNOW_SHOWERS_DAY(23, "h"), + SCATTERED_SNOW_SHOWERS_NIGHT(24, "h"), + SNOW_SHOWERS_SNOW(25, "g"), + MIXED_RAIN_HAIL_RAIN_SLEET(26, "h"), + SLEET_HAIL(27, "h"), + TORNADO(28, "l"), + TROPICAL_STORM_HURRICANE(29, "m"), + WINDY_BREEZY(30, "k"), + WINTRY_MIX_RAIN_SNOW(31, "h"); companion object { fun fromInt(value: Int) = values().firstOrNull { it.id == value } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 140db7b7a0b7..62a98d7a48ea 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -377,12 +377,15 @@ constructor( MutableStateFlow(false) } - val inAllowedKeyguardState = - keyguardTransitionInteractor.startedKeyguardTransitionStep.map { - it.to == KeyguardState.LOCKSCREEN || it.to == KeyguardState.GLANCEABLE_HUB - } - - allOf(inAllowedDeviceState, inAllowedKeyguardState) + if (v2FlagEnabled()) { + val inAllowedKeyguardState = + keyguardTransitionInteractor.startedKeyguardTransitionStep.map { + it.to == KeyguardState.LOCKSCREEN || it.to == KeyguardState.GLANCEABLE_HUB + } + allOf(inAllowedDeviceState, inAllowedKeyguardState) + } else { + inAllowedDeviceState + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt index 69da67e055fe..1e7bec257432 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt @@ -68,10 +68,4 @@ constructor( emptyFlow() } } - - /** Triggered if a face failure occurs regardless of the mode. */ - val faceFailure: Flow<FailedFaceAuthenticationStatus> = - deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance< - FailedFaceAuthenticationStatus - >() } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt index 38e0503440f9..09936839c590 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -22,7 +22,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.util.kotlin.FlowDumperImpl @@ -49,8 +48,6 @@ class DeviceEntryHapticsInteractor constructor( biometricSettingsRepository: BiometricSettingsRepository, deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor, - deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, - keyguardBypassInteractor: KeyguardBypassInteractor, deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, deviceEntrySourceInteractor: DeviceEntrySourceInteractor, fingerprintPropertyRepository: FingerprintPropertyRepository, @@ -83,7 +80,12 @@ constructor( emit(recentPowerButtonPressThresholdMs * -1L - 1L) } - private val playHapticsOnDeviceEntry: Flow<Boolean> = + /** + * Indicates when success haptics should play when the device is entered. This always occurs on + * successful fingerprint authentications. It also occurs on successful face authentication but + * only if the lockscreen is bypassed. + */ + val playSuccessHapticOnDeviceEntry: Flow<Unit> = deviceEntrySourceInteractor.deviceEntryFromBiometricSource .sample( combine( @@ -93,29 +95,17 @@ constructor( ::Triple, ) ) - .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> + .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> val sideFpsAllowsHaptic = !powerButtonDown && systemClock.uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic if (!allowHaptic) { - logger.d( - "Skip success entry haptic from power button. Recent power button press or button is down." - ) + logger.d("Skip success haptic. Recent power button press or button is down.") } allowHaptic } - - private val playHapticsOnFaceAuthSuccessAndBypassDisabled: Flow<Boolean> = - deviceEntryFaceAuthInteractor.isAuthenticated - .filter { it } - .sample(keyguardBypassInteractor.isBypassAvailable) - .map { !it } - - val playSuccessHaptic: Flow<Unit> = - merge(playHapticsOnDeviceEntry, playHapticsOnFaceAuthSuccessAndBypassDisabled) - .filter { it } // map to Unit .map {} .dumpWhileCollecting("playSuccessHaptic") @@ -123,7 +113,7 @@ constructor( private val playErrorHapticForBiometricFailure: Flow<Unit> = merge( deviceEntryFingerprintAuthInteractor.fingerprintFailure, - deviceEntryBiometricAuthInteractor.faceFailure, + deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, ) // map to Unit .map {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 45801ba3517a..aeb327035c79 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -333,7 +333,7 @@ object KeyguardRootViewBinder { if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { launch { - deviceEntryHapticsInteractor.playSuccessHaptic.collect { + deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry.collect { if (msdlFeedback()) { msdlPlayer?.playToken( MSDLToken.UNLOCK, @@ -474,7 +474,7 @@ object KeyguardRootViewBinder { val transition = blueprintViewModel.currentTransition.value val shouldAnimate = transition != null && transition.config.type.animateNotifChanges if (prevTransition == transition && shouldAnimate) { - logger.w("Skipping; layout during transition") + logger.w("Skipping onNotificationContainerBoundsChanged during transition") return } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index faa6c52162ce..a85b9b04c1ce 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -327,7 +327,8 @@ public class LogModule { @SysUISingleton @KeyguardBlueprintLog public static LogBuffer provideKeyguardBlueprintLog(LogBufferFactory factory) { - return factory.create("KeyguardBlueprintLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardBlueprintLog", 1000); } /** @@ -337,7 +338,8 @@ public class LogModule { @SysUISingleton @KeyguardClockLog public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) { - return factory.create("KeyguardClockLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardClockLog", 1000); } /** @@ -347,7 +349,8 @@ public class LogModule { @SysUISingleton @KeyguardSmallClockLog public static LogBuffer provideKeyguardSmallClockLog(LogBufferFactory factory) { - return factory.create("KeyguardSmallClockLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardSmallClockLog", 1000); } /** @@ -357,7 +360,8 @@ public class LogModule { @SysUISingleton @KeyguardLargeClockLog public static LogBuffer provideKeyguardLargeClockLog(LogBufferFactory factory) { - return factory.create("KeyguardLargeClockLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardLargeClockLog", 1000); } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java index 9d375809786a..02dce406bbee 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -698,6 +698,27 @@ public class MediaSwitchingController List<MediaItem> finalMediaItems = targetMediaDevices.stream() .map(MediaItem::createDeviceMediaItem) .collect(Collectors.toList()); + + boolean shouldAddFirstSeenSelectedDevice = + com.android.media.flags.Flags.enableOutputSwitcherDeviceGrouping(); + + if (shouldAddFirstSeenSelectedDevice) { + finalMediaItems.clear(); + Set<String> selectedDevicesIds = getSelectedMediaDevice().stream() + .map(MediaDevice::getId) + .collect(Collectors.toSet()); + for (MediaDevice targetMediaDevice : targetMediaDevices) { + if (shouldAddFirstSeenSelectedDevice + && selectedDevicesIds.contains(targetMediaDevice.getId())) { + finalMediaItems.add(MediaItem.createDeviceMediaItem( + targetMediaDevice, /* isFirstDeviceInGroup */ true)); + shouldAddFirstSeenSelectedDevice = false; + } else { + finalMediaItems.add(MediaItem.createDeviceMediaItem( + targetMediaDevice, /* isFirstDeviceInGroup */ false)); + } + } + } dividerItems.forEach(finalMediaItems::add); attachConnectNewDeviceItemIfNeeded(finalMediaItems); return finalMediaItems; diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 4753b9ac0457..7bb831baec20 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -687,7 +687,7 @@ constructor( if (!isDeviceEntered) { coroutineScope { launch { - deviceEntryHapticsInteractor.playSuccessHaptic + deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry .sample(sceneInteractor.currentScene) .collect { currentScene -> if (Flags.msdlFeedback()) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 305444f7ab5e..fa17b4fad592 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -30,8 +30,6 @@ import android.graphics.Region; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; import android.view.Display; import android.view.IWindow; @@ -75,7 +73,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; -import com.android.systemui.util.settings.SecureSettings; import dagger.Lazy; @@ -134,7 +131,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final SysuiColorExtractor mColorExtractor; private final NotificationShadeWindowModel mNotificationShadeWindowModel; - private final SecureSettings mSecureSettings; /** * Layout params would be aggregated and dispatched all at once if this is > 0. * @@ -168,7 +164,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW Lazy<SelectedUserInteractor> userInteractor, UserTracker userTracker, NotificationShadeWindowModel notificationShadeWindowModel, - SecureSettings secureSettings, Lazy<CommunalInteractor> communalInteractor, @ShadeDisplayAware LayoutParams shadeWindowLayoutParams) { mContext = context; @@ -186,7 +181,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mBackgroundExecutor = backgroundExecutor; mColorExtractor = colorExtractor; mNotificationShadeWindowModel = notificationShadeWindowModel; - mSecureSettings = secureSettings; // prefix with {slow} to make sure this dumps at the END of the critical section. dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this); mAuthController = authController; @@ -424,7 +418,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW (long) mLpChanged.preferredMaxDisplayRefreshRate); } - if (state.bouncerShowing && !isSecureWindowsDisabled()) { + if (state.bouncerShowing) { mLpChanged.flags |= LayoutParams.FLAG_SECURE; } else { mLpChanged.flags &= ~LayoutParams.FLAG_SECURE; @@ -437,13 +431,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } - private boolean isSecureWindowsDisabled() { - return mSecureSettings.getIntForUser( - Settings.Secure.DISABLE_SECURE_WINDOWS, - 0, - UserHandle.USER_CURRENT) == 1; - } - private void adjustScreenOrientation(NotificationShadeWindowState state) { if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) { if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt index eae2c25d77d8..8228b5533fca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt @@ -555,7 +555,6 @@ constructor( secondary = DEFAULT_INTERNAL_INACTIVE_MODEL, ) - // TODO(b/392886257): Support 3 chips if there's space available. - private const val MAX_VISIBLE_CHIPS = 2 + private const val MAX_VISIBLE_CHIPS = 3 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 6837cb2a6292..76d8fb8d3c15 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -129,8 +129,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateColors() { - if (usesTransparentBackground()) { - mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext()); + if (notificationRowTransparency()) { + if (mIsBlurSupported) { + mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext()); + } else { + mNormalColor = mContext.getColor( + com.android.internal.R.color.materialColorSurfaceContainer); + } } else { mNormalColor = mContext.getColor( com.android.internal.R.color.materialColorSurfaceContainerHigh); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index e8054c07eac8..8105ae0960ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -283,7 +283,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mSelectedUserInteractor, mock(UserTracker.class), mKosmos.getNotificationShadeWindowModel(), - mSecureSettings, mKosmos::getCommunalInteractor, mKosmos.getShadeLayoutParams()); mFeatureFlags = new FakeFeatureFlags(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index 88c2697fe2f2..5c26dac5eb30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -1573,6 +1573,25 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { assertThat(items.get(1).isFirstDeviceInGroup()).isFalse(); } + @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING) + @Test + public void deviceListUpdateWithDifferentDevices_firstSelectedDeviceIsFirstDeviceInGroup() { + when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true); + doReturn(mMediaDevices) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + mMediaSwitchingController.start(mCb); + reset(mCb); + mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + mMediaDevices.clear(); + mMediaDevices.add(mMediaDevice2); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + List<MediaItem> items = mMediaSwitchingController.getMediaItemList(); + assertThat(items.get(0).isFirstDeviceInGroup()).isTrue(); + } + private int getNumberOfConnectDeviceButtons() { int numberOfConnectDeviceButtons = 0; for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 341bd3a38999..a978ecdb3534 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -33,6 +33,8 @@ import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -55,8 +57,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; - import android.app.ActivityManager; import android.app.IActivityManager; import android.app.INotificationManager; @@ -136,7 +136,6 @@ import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -166,7 +165,6 @@ import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvision import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.FakeEventLog; import com.android.systemui.util.settings.FakeGlobalSettings; -import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.Flags; @@ -451,7 +449,6 @@ public class BubblesTest extends SysuiTestCase { () -> mSelectedUserInteractor, mUserTracker, mNotificationShadeWindowModel, - new FakeSettings(), mKosmos::getCommunalInteractor, mKosmos.getShadeLayoutParams() ); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt index 6f570a86b19e..cd4b09c5267a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt @@ -21,7 +21,6 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.dump.dumpManager import com.android.systemui.keyevent.domain.interactor.keyEventInteractor import com.android.systemui.keyguard.data.repository.biometricSettingsRepository -import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.util.time.systemClock @@ -31,8 +30,6 @@ val Kosmos.deviceEntryHapticsInteractor by DeviceEntryHapticsInteractor( biometricSettingsRepository = biometricSettingsRepository, deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor, - deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor, - keyguardBypassInteractor = keyguardBypassInteractor, deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, deviceEntrySourceInteractor = deviceEntrySourceInteractor, fingerprintPropertyRepository = fingerprintPropertyRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt index e639326bd7a1..0e348c88f058 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt @@ -24,21 +24,23 @@ import org.junit.runners.model.Statement class LogWtfHandlerRule : TestRule { - private var started = false - private var handler = ThrowAndFailAtEnd + private var failureLogExemptions = mutableListOf<FailureLogExemption>() override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { - started = true + val handler = TerribleFailureTestHandler() val originalWtfHandler = Log.setWtfHandler(handler) var failure: Throwable? = null try { base.evaluate() } catch (ex: Throwable) { - failure = ex.runAndAddSuppressed { handler.onTestFailure(ex) } + failure = ex } finally { - failure = failure.runAndAddSuppressed { handler.onTestFinished() } + failure = + runAndAddSuppressed(failure) { + handler.onTestFinished(failureLogExemptions) + } Log.setWtfHandler(originalWtfHandler) } if (failure != null) { @@ -48,74 +50,52 @@ class LogWtfHandlerRule : TestRule { } } - fun Throwable?.runAndAddSuppressed(block: () -> Unit): Throwable? { + /** Adds a log failure exemption. Exemptions are evaluated at the end of the test. */ + fun addFailureLogExemption(exemption: FailureLogExemption) { + failureLogExemptions.add(exemption) + } + + /** Clears and sets exemptions. Exemptions are evaluated at the end of the test. */ + fun resetFailureLogExemptions(vararg exemptions: FailureLogExemption) { + failureLogExemptions = exemptions.toMutableList() + } + + private fun runAndAddSuppressed(currentError: Throwable?, block: () -> Unit): Throwable? { try { block() } catch (t: Throwable) { - if (this == null) { + if (currentError == null) { return t } - addSuppressed(t) + currentError.addSuppressed(t) } - return this + return currentError } - fun setWtfHandler(handler: TerribleFailureTestHandler) { - check(!started) { "Should only be called before the test starts" } - this.handler = handler - } - - fun interface TerribleFailureTestHandler : TerribleFailureHandler { - fun onTestFailure(failure: Throwable) {} - fun onTestFinished() {} - } - - companion object Handlers { - val ThrowAndFailAtEnd - get() = - object : TerribleFailureTestHandler { - val failures = mutableListOf<Log.TerribleFailure>() - - override fun onTerribleFailure( - tag: String, - what: Log.TerribleFailure, - system: Boolean - ) { - failures.add(what) - throw what - } + private class TerribleFailureTestHandler : TerribleFailureHandler { + private val failureLogs = mutableListOf<FailureLog>() - override fun onTestFailure(failure: Throwable) { - super.onTestFailure(failure) - } + override fun onTerribleFailure(tag: String, what: Log.TerribleFailure, system: Boolean) { + failureLogs.add(FailureLog(tag = tag, failure = what, system = system)) + } - override fun onTestFinished() { - if (failures.isNotEmpty()) { - throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) - } - } + fun onTestFinished(exemptions: List<FailureLogExemption>) { + val failures = + failureLogs.filter { failureLog -> + !exemptions.any { it.isFailureLogExempt(failureLog) } } + if (failures.isNotEmpty()) { + throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0].failure) + } + } + } - val JustThrow = TerribleFailureTestHandler { _, what, _ -> throw what } - - val JustFailAtEnd - get() = - object : TerribleFailureTestHandler { - val failures = mutableListOf<Log.TerribleFailure>() - - override fun onTerribleFailure( - tag: String, - what: Log.TerribleFailure, - system: Boolean - ) { - failures.add(what) - } + /** All the information from a call to [Log.wtf] that was handed to [TerribleFailureHandler] */ + data class FailureLog(val tag: String, val failure: Log.TerribleFailure, val system: Boolean) - override fun onTestFinished() { - if (failures.isNotEmpty()) { - throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) - } - } - } + /** An interface for exempting a [FailureLog] from causing a test failure. */ + fun interface FailureLogExemption { + /** Determines whether a log should be except from failing the test. */ + fun isFailureLogExempt(log: FailureLog): Boolean } } diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 9dd09cef88f9..40085ed89294 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -113,7 +113,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE, - DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount); + accessCount); } /** @@ -257,8 +257,7 @@ final class AttributedOp { if (isStarted) { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, startTime, - attributionFlags, attributionChainId, - DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1); + attributionFlags, attributionChainId, 1); } } @@ -344,9 +343,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), - event.getAttributionFlags(), event.getAttributionChainId(), - isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP - : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP); + event.getAttributionFlags(), event.getAttributionChainId()); if (!isPausing) { mAppOpsService.mInProgressStartOpEventPool.release(event); @@ -454,7 +451,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), startTime, event.getAttributionFlags(), - event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1); + event.getAttributionChainId(), 1); if (shouldSendActive) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), true, diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java index 12c35ae92cbe..b7599f6e40c3 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -46,22 +46,17 @@ import static android.app.AppOpsManager.OP_WRITE_SMS; import static java.lang.Long.min; import static java.lang.Math.max; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.os.AsyncTask; import android.os.Build; -import android.permission.flags.Flags; import android.provider.DeviceConfig; import android.util.Slog; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FrameworkStatsLog; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.time.Duration; import java.util.Date; @@ -134,27 +129,6 @@ abstract class DiscreteOpsRegistry { boolean mDebugMode = false; - static final int ACCESS_TYPE_NOTE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP; - static final int ACCESS_TYPE_START_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP; - static final int ACCESS_TYPE_FINISH_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP; - static final int ACCESS_TYPE_PAUSE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP; - static final int ACCESS_TYPE_RESUME_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"ACCESS_TYPE_"}, value = { - ACCESS_TYPE_NOTE_OP, - ACCESS_TYPE_START_OP, - ACCESS_TYPE_FINISH_OP, - ACCESS_TYPE_PAUSE_OP, - ACCESS_TYPE_RESUME_OP - }) - @interface AccessType {} - void systemReady() { DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> { @@ -166,8 +140,7 @@ abstract class DiscreteOpsRegistry { abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, - @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteOpsRegistry.AccessType int accessType); + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId); /** * A periodic callback from {@link AppOpsService} to flush the in memory events to disk. @@ -227,9 +200,6 @@ abstract class DiscreteOpsRegistry { return true; } - // could this be impl detail of discrete registry, just one test is using the method - // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps(); - private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) { sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF, @@ -277,28 +247,4 @@ abstract class DiscreteOpsRegistry { } return result; } - - /** - * Whether app op access tacking is enabled and a metric event should be logged. - */ - static boolean shouldLogAccess(int op) { - return Flags.appopAccessTrackingLoggingEnabled() - && ArrayUtils.contains(sDiscreteOpsToLog, op); - } - - String getAttributionTag(String attributionTag, String packageName) { - if (attributionTag == null || packageName == null) { - return attributionTag; - } - int firstChar = 0; - if (attributionTag.startsWith(packageName)) { - firstChar = packageName.length(); - if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) - == '.') { - firstChar++; - } - } - return attributionTag.substring(firstChar); - } - } diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java index dc11be9aadb6..c897891d02c3 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -36,7 +36,6 @@ import android.util.IntArray; import android.util.LongSparseArray; import android.util.Slog; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.ServiceThread; import java.io.File; @@ -97,15 +96,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, int flags, int uidState, - long accessTime, long accessDuration, int attributionFlags, int attributionChainId, - int accessType) { - if (shouldLogAccess(op)) { - FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, - uidState, flags, attributionFlags, - getAttributionTag(attributionTag, packageName), - attributionChainId); - } - + long accessTime, long accessDuration, int attributionFlags, int attributionChainId) { if (!isDiscreteOp(op, flags)) { return; } diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java index 1523cca86607..909a04c44ae5 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java @@ -48,15 +48,13 @@ class DiscreteOpsTestingShim extends DiscreteOpsRegistry { @Override void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, int flags, int uidState, long accessTime, - long accessDuration, int attributionFlags, int attributionChainId, int accessType) { + long accessDuration, int attributionFlags, int attributionChainId) { long start = SystemClock.uptimeMillis(); mXmlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, - uidState, accessTime, accessDuration, attributionFlags, attributionChainId, - accessType); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); long start2 = SystemClock.uptimeMillis(); mSqlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, - uidState, accessTime, accessDuration, attributionFlags, attributionChainId, - accessType); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); long end = SystemClock.uptimeMillis(); long xmlTimeTaken = start2 - start; long sqlTimeTaken = end - start2; diff --git a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java index a6e3fc7cc66a..20706b659ffb 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java @@ -45,7 +45,6 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -159,15 +158,7 @@ class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry { void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, - @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @AccessType int accessType) { - if (shouldLogAccess(op)) { - FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, - uidState, flags, attributionFlags, - getAttributionTag(attributionTag, packageName), - attributionChainId); - } - + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { if (!isDiscreteOp(op, flags)) { return; } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index d267e0d9e536..06e43e8ec68d 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -497,7 +497,7 @@ final class HistoricalRegistry { @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteOpsRegistry.AccessType int accessType, int accessCount) { + int accessCount) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -510,7 +510,7 @@ final class HistoricalRegistry { mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, uidState, accessTime, -1, attributionFlags, - attributionChainId, accessType); + attributionChainId); } } } @@ -533,8 +533,7 @@ final class HistoricalRegistry { void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long eventStartTime, long increment, - @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteOpsRegistry.AccessType int accessType) { + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -546,7 +545,7 @@ final class HistoricalRegistry { attributionTag, uidState, flags, increment); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, uidState, eventStartTime, increment, - attributionFlags, attributionChainId, accessType); + attributionFlags, attributionChainId); } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 7016c11b69e7..a28069bbf050 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -674,9 +674,9 @@ public final class DisplayManagerService extends SystemService { mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null); mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName); - + // TODO(b/400384229): stats service needs to react to mirror-extended switch mExternalDisplayStatsService = new ExternalDisplayStatsService(mContext, mHandler, - this::isExtendedDisplayEnabled); + this::isExtendedDisplayAllowed); mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext, mExternalDisplayStatsService); mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector()); @@ -690,7 +690,7 @@ public final class DisplayManagerService extends SystemService { deliverTopologyUpdate(update.first); }; mDisplayTopologyCoordinator = new DisplayTopologyCoordinator( - this::isExtendedDisplayEnabled, topologyChangedCallback, + this::isExtendedDisplayAllowed, topologyChangedCallback, new HandlerExecutor(mHandler), mSyncRoot, backupManager::dataChanged); } else { mDisplayTopologyCoordinator = null; @@ -2411,7 +2411,10 @@ public final class DisplayManagerService extends SystemService { updateLogicalDisplayState(display); } - private boolean isExtendedDisplayEnabled() { + private boolean isExtendedDisplayAllowed() { + if (mFlags.isDisplayContentModeManagementEnabled()) { + return true; + } try { return 0 != Settings.Global.getInt( mContext.getContentResolver(), @@ -6045,7 +6048,13 @@ public final class DisplayManagerService extends SystemService { return; } if (inTopology) { - mDisplayTopologyCoordinator.onDisplayAdded(getDisplayInfo(displayId)); + var info = getDisplayInfo(displayId); + if (info == null) { + Slog.w(TAG, "onDisplayBelongToTopologyChanged: cancelled displayId=" + + displayId + " info=null"); + return; + } + mDisplayTopologyCoordinator.onDisplayAdded(info); } else { mDisplayTopologyCoordinator.onDisplayRemoved(displayId); } diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index 997fff58b952..b4df1f76dccb 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -69,9 +69,9 @@ class DisplayTopologyCoordinator { private final SparseArray<String> mDisplayIdToUniqueIdMapping = new SparseArray<>(); /** - * Check if extended displays are enabled. If not, a topology is not needed. + * Check if extended displays are allowed. If not, a topology is not needed. */ - private final BooleanSupplier mIsExtendedDisplayEnabled; + private final BooleanSupplier mIsExtendedDisplayAllowed; /** * Callback used to send topology updates. @@ -83,21 +83,21 @@ class DisplayTopologyCoordinator { private final DisplayManagerService.SyncRoot mSyncRoot; private final Runnable mTopologySavedCallback; - DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled, + DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayAllowed, Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, Runnable topologySavedCallback) { - this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback, + this(new Injector(), isExtendedDisplayAllowed, onTopologyChangedCallback, topologyChangeExecutor, syncRoot, topologySavedCallback); } @VisibleForTesting - DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled, + DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayAllowed, Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, Runnable topologySavedCallback) { mTopology = injector.getTopology(); - mIsExtendedDisplayEnabled = isExtendedDisplayEnabled; + mIsExtendedDisplayAllowed = isExtendedDisplayAllowed; mOnTopologyChangedCallback = onTopologyChangedCallback; mTopologyChangeExecutor = topologyChangeExecutor; mSyncRoot = syncRoot; @@ -262,9 +262,9 @@ class DisplayTopologyCoordinator { return false; } if ((info.type == Display.TYPE_EXTERNAL || info.type == Display.TYPE_OVERLAY) - && !mIsExtendedDisplayEnabled.getAsBoolean()) { + && !mIsExtendedDisplayAllowed.getAsBoolean()) { Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " - + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayEnabled"); + + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayAllowed"); return false; } return true; diff --git a/services/core/java/com/android/server/power/ScreenUndimDetector.java b/services/core/java/com/android/server/power/ScreenUndimDetector.java index c4929c210e2c..b376417061db 100644 --- a/services/core/java/com/android/server/power/ScreenUndimDetector.java +++ b/services/core/java/com/android/server/power/ScreenUndimDetector.java @@ -30,11 +30,11 @@ import android.provider.DeviceConfig; import android.util.Slog; import android.view.Display; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import java.util.Set; -import java.util.concurrent.TimeUnit; /** * Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen @@ -48,7 +48,6 @@ public class ScreenUndimDetector { /** DeviceConfig flag: is keep screen on feature enabled. */ static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled"; - private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true; private static final int OUTCOME_POWER_BUTTON = FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON; private static final int OUTCOME_TIMEOUT = @@ -58,15 +57,11 @@ public class ScreenUndimDetector { /** DeviceConfig flag: how long should we keep the screen on. */ @VisibleForTesting static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis"; - @VisibleForTesting - static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10); private long mKeepScreenOnForMillis; /** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */ @VisibleForTesting static final String KEY_UNDIMS_REQUIRED = "undims_required"; - @VisibleForTesting - static final int DEFAULT_UNDIMS_REQUIRED = 2; private int mUndimsRequired; /** @@ -76,8 +71,6 @@ public class ScreenUndimDetector { @VisibleForTesting static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = "max_duration_between_undims_millis"; - @VisibleForTesting - static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5); private long mMaxDurationBetweenUndimsMillis; @VisibleForTesting @@ -92,6 +85,7 @@ public class ScreenUndimDetector { private long mUndimOccurredTime = -1; private long mInteractionAfterUndimTime = -1; private InternalClock mClock; + private Context mContext; public ScreenUndimDetector() { mClock = new InternalClock(); @@ -109,12 +103,13 @@ public class ScreenUndimDetector { /** Should be called in parent's systemReady() */ public void systemReady(Context context) { + mContext = context; readValuesFromDeviceConfig(); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE, - context.getMainExecutor(), + mContext.getMainExecutor(), (properties) -> onDeviceConfigChange(properties.getKeyset())); - final PowerManager powerManager = context.getSystemService(PowerManager.class); + final PowerManager powerManager = mContext.getSystemService(PowerManager.class); mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, UNDIM_DETECTOR_WAKE_LOCK); @@ -203,36 +198,44 @@ public class ScreenUndimDetector { } } - private boolean readKeepScreenOnNotificationEnabled() { + private boolean readKeepScreenOnEnabled() { + boolean defaultKeepScreenOnEnabled = mContext.getResources().getBoolean( + R.bool.config_defaultPreventScreenTimeoutEnabled); return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_KEEP_SCREEN_ON_ENABLED, - DEFAULT_KEEP_SCREEN_ON_ENABLED); + defaultKeepScreenOnEnabled); } private long readKeepScreenOnForMillis() { + long defaultKeepScreenOnDuration = mContext.getResources().getInteger( + R.integer.config_defaultPreventScreenTimeoutForMillis); return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_KEEP_SCREEN_ON_FOR_MILLIS, - DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS); + defaultKeepScreenOnDuration); } private int readUndimsRequired() { + int defaultUndimsRequired = mContext.getResources().getInteger( + R.integer.config_defaultUndimsRequired); int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_UNDIMS_REQUIRED, - DEFAULT_UNDIMS_REQUIRED); + defaultUndimsRequired); if (undimsRequired < 1 || undimsRequired > 5) { Slog.e(TAG, "Provided undimsRequired=" + undimsRequired - + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED); - return DEFAULT_UNDIMS_REQUIRED; + + " is not allowed [1, 5]; using the default=" + defaultUndimsRequired); + return defaultUndimsRequired; } return undimsRequired; } private long readMaxDurationBetweenUndimsMillis() { + long defaultMaxDurationBetweenUndimsMillis = mContext.getResources().getInteger( + R.integer.config_defaultMaxDurationBetweenUndimsMillis); return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS, - DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS); + defaultMaxDurationBetweenUndimsMillis); } private void onDeviceConfigChange(@NonNull Set<String> keys) { @@ -253,15 +256,16 @@ public class ScreenUndimDetector { @VisibleForTesting void readValuesFromDeviceConfig() { - mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled(); + mKeepScreenOnEnabled = readKeepScreenOnEnabled(); mKeepScreenOnForMillis = readKeepScreenOnForMillis(); mUndimsRequired = readUndimsRequired(); mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis(); Slog.i(TAG, "readValuesFromDeviceConfig():" + "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis - + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled - + "\nmUndimsRequired=" + mUndimsRequired); + + "\nmKeepScreenOnEnabled=" + mKeepScreenOnEnabled + + "\nmUndimsRequired=" + mUndimsRequired + + "\nmMaxDurationBetweenUndimsMillis=" + mMaxDurationBetweenUndimsMillis); } diff --git a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java index 5563f98e8842..7cd9bdbc662c 100644 --- a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java +++ b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java @@ -374,6 +374,10 @@ public class BatteryHistoryDirectory implements BatteryStatsHistory.BatteryHisto @SuppressWarnings("unchecked") @Override public List<BatteryHistoryFragment> getFragments() { + if (!mLock.isHeldByCurrentThread()) { + throw new IllegalStateException("Reading battery history without a lock"); + } + ensureInitialized(); return (List<BatteryHistoryFragment>) (List<? extends BatteryHistoryFragment>) mHistoryFiles; @@ -443,44 +447,6 @@ public class BatteryHistoryDirectory implements BatteryStatsHistory.BatteryHisto } @Override - public BatteryHistoryFragment getNextFragment(BatteryHistoryFragment current, long startTimeMs, - long endTimeMs) { - ensureInitialized(); - - if (!mLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Iterating battery history without a lock"); - } - - int nextFileIndex = 0; - int firstFileIndex = 0; - // skip the last file because its data is in history buffer. - int lastFileIndex = mHistoryFiles.size() - 2; - for (int i = lastFileIndex; i >= 0; i--) { - BatteryHistoryFragment fragment = mHistoryFiles.get(i); - if (current != null && fragment.monotonicTimeMs == current.monotonicTimeMs) { - nextFileIndex = i + 1; - } - if (fragment.monotonicTimeMs > endTimeMs) { - lastFileIndex = i - 1; - } - if (fragment.monotonicTimeMs <= startTimeMs) { - firstFileIndex = i; - break; - } - } - - if (nextFileIndex < firstFileIndex) { - nextFileIndex = firstFileIndex; - } - - if (nextFileIndex <= lastFileIndex) { - return mHistoryFiles.get(nextFileIndex); - } - - return null; - } - - @Override public boolean hasCompletedFragments() { ensureInitialized(); diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 21628341ea62..cb122f2080a2 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -107,8 +107,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord && !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go setSnapshotEnabled(snapshotEnabled); mSnapshotPersistQueue = persistQueue; - mPersistInfoProvider = createPersistInfoProvider(service, - Environment::getDataSystemCeDirectory); + mPersistInfoProvider = createPersistInfoProvider(service); mPersister = new TaskSnapshotPersister( persistQueue, mPersistInfoProvider, @@ -117,6 +116,11 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord initialize(new ActivitySnapshotCache()); } + @VisibleForTesting + PersistInfoProvider createPersistInfoProvider(WindowManagerService service) { + return createPersistInfoProvider(service, Environment::getDataSystemCeDirectory); + } + @Override protected float initSnapshotScale() { final float config = mService.mContext.getResources().getFloat( diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java index 5db02dff8351..aaa5a0074506 100644 --- a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java @@ -16,10 +16,20 @@ package com.android.server.wm; +import static com.android.server.wm.AbsAppSnapshotController.TAG; + import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.window.TaskSnapshot; +import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; + import java.io.File; +import java.util.UUID; class BaseAppSnapshotPersister { static final String LOW_RES_FILE_POSTFIX = "_reduced"; @@ -29,6 +39,7 @@ class BaseAppSnapshotPersister { // Shared with SnapshotPersistQueue protected final Object mLock; protected final SnapshotPersistQueue mSnapshotPersistQueue; + @VisibleForTesting protected final PersistInfoProvider mPersistInfoProvider; BaseAppSnapshotPersister(SnapshotPersistQueue persistQueue, @@ -79,6 +90,8 @@ class BaseAppSnapshotPersister { private final boolean mEnableLowResSnapshots; private final float mLowResScaleFactor; private final boolean mUse16BitFormat; + private final SparseBooleanArray mInitializedUsers = new SparseBooleanArray(); + private final SparseArray<File> mScrambleDirectories = new SparseArray<>(); PersistInfoProvider(DirectoryResolver directoryResolver, String dirName, boolean enableLowResSnapshots, float lowResScaleFactor, boolean use16BitFormat) { @@ -91,9 +104,80 @@ class BaseAppSnapshotPersister { @NonNull File getDirectory(int userId) { + if (Flags.scrambleSnapshotFileName()) { + final File directory = getOrInitScrambleDirectory(userId); + if (directory != null) { + return directory; + } + } + return getBaseDirectory(userId); + } + + @NonNull + private File getBaseDirectory(int userId) { return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), mDirName); } + @Nullable + private File getOrInitScrambleDirectory(int userId) { + synchronized (mScrambleDirectories) { + if (mInitializedUsers.get(userId)) { + return mScrambleDirectories.get(userId); + } + mInitializedUsers.put(userId, true); + final File scrambledDirectory = getScrambleDirectory(userId); + final File baseDir = getBaseDirectory(userId); + String newName = null; + // If directory exists, rename + if (scrambledDirectory.exists()) { + newName = UUID.randomUUID().toString(); + final File scrambleTo = new File(baseDir, newName); + if (!scrambledDirectory.renameTo(scrambleTo)) { + Slog.w(TAG, "SnapshotPersister rename scramble folder fail."); + return null; + } + } else { + // If directory not exists, mkDir. + if (!baseDir.exists() && !baseDir.mkdir()) { + Slog.w(TAG, "SnapshotPersister make base folder fail."); + return null; + } + if (!scrambledDirectory.mkdir()) { + Slog.e(TAG, "SnapshotPersister make scramble folder fail"); + return null; + } + // Move any existing files to this folder. + final String[] files = baseDir.list(); + if (files != null) { + for (String file : files) { + final File original = new File(baseDir, file); + if (original.isDirectory()) { + newName = file; + } else { + File to = new File(scrambledDirectory, file); + original.renameTo(to); + } + } + } + } + final File newFolder = new File(baseDir, newName); + mScrambleDirectories.put(userId, newFolder); + return newFolder; + } + } + + @NonNull + private File getScrambleDirectory(int userId) { + final File dir = getBaseDirectory(userId); + final String[] directories = dir.list( + (current, name) -> new File(current, name).isDirectory()); + if (directories != null && directories.length > 0) { + return new File(dir, directories[0]); + } else { + return new File(dir, UUID.randomUUID().toString()); + } + } + /** * Return if task snapshots are stored in 16 bit pixel format. * diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java index fa7a99d55896..d90fff229cd9 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java @@ -40,14 +40,14 @@ class DisplayWindowListenerController { } int[] registerListener(IDisplayWindowListener listener) { + mDisplayListeners.register(listener); + final IntArray displayIds = new IntArray(); synchronized (mService.mGlobalLock) { - mDisplayListeners.register(listener); - final IntArray displayIds = new IntArray(); mService.mAtmService.mRootWindowContainer.forAllDisplays((displayContent) -> { displayIds.add(displayContent.mDisplayId); }); - return displayIds.toArray(); } + return displayIds.toArray(); } void unregisterListener(IDisplayWindowListener listener) { 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 index afb18f5be669..5c9ba401bf88 100644 --- 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 @@ -32,7 +32,7 @@ internal class IgnoreableExpect : TestRule { private var ignore = false - override fun apply(base: Statement?, description: Description?): Statement { + override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { ignore = false diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt index 206c90d0481a..6dc7361e5366 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -47,7 +47,7 @@ class DisplayTopologyCoordinatorTest { private val mockTopology = mock<DisplayTopology>() private val mockTopologyCopy = mock<DisplayTopology>() private val mockTopologyGraph = mock<DisplayTopologyGraph>() - private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>() + private val mockIsExtendedDisplayAllowed = mock<() -> Boolean>() private val mockTopologySavedCallback = mock<() -> Unit>() private val mockTopologyChangedCallback = mock<(android.util.Pair<DisplayTopology, DisplayTopologyGraph>) -> Unit>() @@ -73,10 +73,10 @@ class DisplayTopologyCoordinatorTest { ) = mockTopologyStore } - whenever(mockIsExtendedDisplayEnabled()).thenReturn(true) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(true) whenever(mockTopology.copy()).thenReturn(mockTopologyCopy) whenever(mockTopologyCopy.getGraph(any())).thenReturn(mockTopologyGraph) - coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled, + coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayAllowed, mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot(), mockTopologySavedCallback) } @@ -195,7 +195,7 @@ class DisplayTopologyCoordinatorTest { @Test fun addDisplay_external_extendedDisplaysDisabled() { - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) for (displayInfo in displayInfos) { coordinator.onDisplayAdded(displayInfo) @@ -208,7 +208,7 @@ class DisplayTopologyCoordinatorTest { @Test fun addDisplay_overlay_extendedDisplaysDisabled() { displayInfos[0].type = Display.TYPE_OVERLAY - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) for (displayInfo in displayInfos) { coordinator.onDisplayAdded(displayInfo) @@ -314,7 +314,7 @@ class DisplayTopologyCoordinatorTest { @Test fun updateDisplay_external_extendedDisplaysDisabled() { - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) for (displayInfo in displayInfos) { coordinator.onDisplayChanged(displayInfo) @@ -328,7 +328,7 @@ class DisplayTopologyCoordinatorTest { @Test fun updateDisplay_overlay_extendedDisplaysDisabled() { displayInfos[0].type = Display.TYPE_OVERLAY - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) coordinator.onDisplayChanged(displayInfos[0]) diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java index 8ce05e2fa115..c9f86b04be22 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java @@ -23,7 +23,6 @@ import static android.hardware.display.DisplayManagerInternal.DisplayPowerReques import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; import static android.view.Display.DEFAULT_DISPLAY_GROUP; -import static com.android.server.power.ScreenUndimDetector.DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS; import static com.android.server.power.ScreenUndimDetector.KEY_KEEP_SCREEN_ON_ENABLED; import static com.android.server.power.ScreenUndimDetector.KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS; import static com.android.server.power.ScreenUndimDetector.KEY_UNDIMS_REQUIRED; @@ -49,6 +48,7 @@ import org.junit.runners.JUnit4; import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Tests for {@link com.android.server.power.ScreenUndimDetector} @@ -61,7 +61,8 @@ public class ScreenUndimDetectorTest { POLICY_DIM, POLICY_BRIGHT); private static final int OTHER_DISPLAY_GROUP = DEFAULT_DISPLAY_GROUP + 1; - + private static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = + TimeUnit.MINUTES.toMillis(5); @ClassRule public static final TestableContext sContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @@ -88,7 +89,8 @@ public class ScreenUndimDetectorTest { @Before public void setup() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - + DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, + KEY_KEEP_SCREEN_ON_ENABLED, Boolean.TRUE.toString(), false /*makeDefault*/); DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_UNDIMS_REQUIRED, Integer.toString(1), false /*makeDefault*/); @@ -108,10 +110,10 @@ public class ScreenUndimDetectorTest { @Test public void recordScreenPolicy_disabledByFlag_noop() { + setup(); DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/); mScreenUndimDetector.readValuesFromDeviceConfig(); - mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_DIM); mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_BRIGHT); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index 5165e34c7fcd..fc864dd230d9 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; @@ -330,31 +331,24 @@ public class BatteryStatsHistoryTest { return invocation.callRealMethod(); }).when(mHistory).readFragmentToParcel(any(), any()); - // Prepare history for iteration - mHistory.iterate(0, MonotonicClock.UNDEFINED); - - Parcel parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("123.bh"); - - // Skip to the end to force reading the next parcel - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("1000.bh"); - - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("2000.bh"); + int eventsRead = 0; + BatteryStatsHistoryIterator iterator = mHistory.iterate(0, MonotonicClock.UNDEFINED); + while (iterator.hasNext()) { + HistoryItem item = iterator.next(); + if (item.eventCode == HistoryItem.EVENT_JOB_START) { + eventsRead++; + assertThat(mReadFiles).containsExactly("123.bh"); + } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) { + eventsRead++; + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh"); + } else if (item.eventCode == HistoryItem.EVENT_ALARM) { + eventsRead++; + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh"); + } + } - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNull(); - assertThat(mReadFiles).isEmpty(); + assertThat(eventsRead).isEqualTo(3); + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh", "3000.bh"); } @Test @@ -372,25 +366,19 @@ public class BatteryStatsHistoryTest { return invocation.callRealMethod(); }).when(mHistory).readFragmentToParcel(any(), any()); - // Prepare history for iteration - mHistory.iterate(1000, 3000); - - Parcel parcel = mHistory.getNextParcel(1000, 3000); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("1000.bh"); - - // Skip to the end to force reading the next parcel - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(1000, 3000); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("2000.bh"); + BatteryStatsHistoryIterator iterator = mHistory.iterate(1000, 3000); + while (iterator.hasNext()) { + HistoryItem item = iterator.next(); + if (item.eventCode == HistoryItem.EVENT_JOB_START) { + fail("Event outside the range"); + } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) { + assertThat(mReadFiles).containsExactly("1000.bh"); + } else if (item.eventCode == HistoryItem.EVENT_ALARM) { + fail("Event outside the range"); + } + } - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(1000, 3000); - assertThat(parcel).isNull(); - assertThat(mReadFiles).isEmpty(); + assertThat(mReadFiles).containsExactly("1000.bh", "2000.bh"); } private void prepareMultiFileHistory() { diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java index ae973be17904..56f802b278c6 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java @@ -89,8 +89,7 @@ public class DiscreteAppOpXmlPersistenceTest { int attributionChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, - uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP); + uidState, accessTime, duration, attributionFlags, attributionChainId); // Verify in-memory object is correct fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, @@ -121,8 +120,7 @@ public class DiscreteAppOpXmlPersistenceTest { int attributionChainId = 10; mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, - uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP); + uidState, accessTime, duration, attributionFlags, attributionChainId); fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, duration, uidState, opFlags, attributionFlags, attributionChainId); diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java index 8eea1c73d4f2..6c66f149baa7 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java @@ -70,7 +70,7 @@ public class DiscreteOpsMigrationAndRollbackTest { opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), opEvent.getDuration(), opEvent.getAttributionFlags(), - (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + (int) opEvent.getChainId()); } xmlRegistry.writeAndClearOldAccessHistory(); assertThat(xmlRegistry.readLargestChainIdFromDiskLocked()).isEqualTo(RECORD_COUNT); @@ -104,7 +104,7 @@ public class DiscreteOpsMigrationAndRollbackTest { opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), opEvent.getDuration(), opEvent.getAttributionFlags(), - (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + (int) opEvent.getChainId()); } // flush records from cache to the database. sqlRegistry.shutdown(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java index 948371f74a9c..ad706e879b72 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java @@ -63,11 +63,23 @@ public class ActivitySnapshotControllerTests extends TaskSnapshotPersisterTestBa super(0.8f /* highResScale */, 0.5f /* lowResScale */); } + private class TestActivitySnapshotController extends ActivitySnapshotController { + TestActivitySnapshotController(WindowManagerService service, + SnapshotPersistQueue persistQueue) { + super(service, persistQueue); + } + @Override + BaseAppSnapshotPersister.PersistInfoProvider createPersistInfoProvider( + WindowManagerService service) { + return mPersister.mPersistInfoProvider; + } + } @Override @Before public void setUp() { super.setUp(); - mActivitySnapshotController = new ActivitySnapshotController(mWm, mSnapshotPersistQueue); + mActivitySnapshotController = new TestActivitySnapshotController( + mWm, mSnapshotPersistQueue); spyOn(mActivitySnapshotController); doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots(); mActivitySnapshotController.resetTmpFields(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java index 51ea498811fc..f22ecb5eb3f9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java @@ -68,11 +68,8 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas public void testPersistAndLoadSnapshot() { mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] files = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")}; + final File[] files = convertFilePath("1.proto", "1.jpg"); + final File[] nonExistsFiles = convertFilePath("1_reduced.proto"); assertTrueForFiles(files, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */); @@ -92,14 +89,9 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas taskIds.add(1); mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg"); + final File[] nonExistsFiles = convertFilePath("1_reduced.proto", "2.proto", "2.jpg", + "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } @@ -112,14 +104,8 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "2.proto", "2.jpg"); + final File[] nonExistsFiles = convertFilePath("1_reduced.jpg", "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java index 4b54e4464ca7..af06c14516a1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java @@ -75,9 +75,7 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa public void testPersistAndLoadSnapshot() { mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] files = new File[]{new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")}; + final File[] files = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg"); assertTrueForFiles(files, File::exists, " must exist"); final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */); assertNotNull(snapshot); @@ -140,13 +138,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa mSnapshotPersistQueue.waitForQueueEmpty(); // Make sure 1,2 were purged but removeObsoleteFiles wasn't. - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/3.proto"), - new File(FILES_DIR.getPath() + "/snapshots/4.proto")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/100.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.proto")}; + final File[] existsFiles = convertFilePath("3.proto", "4.proto"); + final File[] nonExistsFiles = convertFilePath("100.proto", "1.proto", "2.proto"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } @@ -427,14 +420,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa taskIds.add(1); mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg"); + final File[] nonExistsFiles = convertFilePath("2.proto", "2.jpg", "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } @@ -447,13 +434,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg", "2.proto", + "2.jpg", "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index 1e16c97de647..b2c195e8ebaa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.content.ComponentName; import android.content.ContextWrapper; import android.content.res.Resources; @@ -46,6 +47,7 @@ import android.window.TaskSnapshot; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.AfterClass; @@ -129,12 +131,33 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { return; } for (File file : files) { - if (!file.isDirectory()) { - file.delete(); + if (file.isDirectory()) { + final File[] subFiles = file.listFiles(); + if (subFiles == null) { + continue; + } + for (File subFile : subFiles) { + subFile.delete(); + } } + file.delete(); } } + File[] convertFilePath(@NonNull String... fileNames) { + final File[] files = new File[fileNames.length]; + final String path; + if (Flags.scrambleSnapshotFileName()) { + path = mPersister.mPersistInfoProvider.getDirectory(mTestUserId).getPath(); + } else { + path = FILES_DIR.getPath() + "/snapshots/"; + } + for (int i = 0; i < fileNames.length; i++) { + files[i] = new File(path + fileNames[i]); + } + return files; + } + TaskSnapshot createSnapshot() { return new TaskSnapshotBuilder().setTopActivityComponent(getUniqueComponentName()).build(); } diff --git a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt index 4995eebdd79e..fe72dae9ff34 100644 --- a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt +++ b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt @@ -155,35 +155,35 @@ class IntDefProcessor : AbstractProcessor() { ) { val indent = " " - writer.appendln("{") + writer.appendLine("{") val intDefTypesCount = annotationTypeToIntDefMapping.size var currentIntDefTypesCount = 0 for ((field, intDefMapping) in annotationTypeToIntDefMapping) { - writer.appendln("""$indent"$field": {""") + writer.appendLine("""$indent"$field": {""") // Start IntDef - writer.appendln("""$indent$indent"flag": ${intDefMapping.flag},""") + writer.appendLine("""$indent$indent"flag": ${intDefMapping.flag},""") - writer.appendln("""$indent$indent"values": {""") + writer.appendLine("""$indent$indent"values": {""") intDefMapping.entries.joinTo(writer, separator = ",\n") { (value, identifier) -> """$indent$indent$indent"$value": "$identifier"""" } - writer.appendln() - writer.appendln("$indent$indent}") + writer.appendLine() + writer.appendLine("$indent$indent}") // End IntDef writer.append("$indent}") if (++currentIntDefTypesCount < intDefTypesCount) { - writer.appendln(",") + writer.appendLine(",") } else { - writer.appendln("") + writer.appendLine("") } } - writer.appendln("}") + writer.appendLine("}") } } } |