diff options
85 files changed, 2131 insertions, 966 deletions
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 6e4c28f3eca6..7a811a1cdfb8 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -28,6 +28,7 @@ per-file ProfilerInfo* = file:/ACTIVITY_MANAGER_OWNERS per-file Service* = file:/ACTIVITY_MANAGER_OWNERS per-file SystemServiceRegistry.java = file:/ACTIVITY_MANAGER_OWNERS per-file *UserSwitchObserver* = file:/ACTIVITY_MANAGER_OWNERS +per-file UidObserver* = file:/ACTIVITY_MANAGER_OWNERS # UI Automation per-file *UiAutomation* = file:/services/accessibility/OWNERS diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 5514868a32f5..382f9bee5eeb 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -273,11 +273,7 @@ public final class Slice implements Parcelable { protected Slice(Parcel in) { mHints = in.readStringArray(); - int n = in.readInt(); - mItems = new SliceItem[n]; - for (int i = 0; i < n; i++) { - mItems[i] = SliceItem.CREATOR.createFromParcel(in); - } + mItems = in.createTypedArray(SliceItem.CREATOR); mUri = Uri.CREATOR.createFromParcel(in); mSpec = in.readTypedObject(SliceSpec.CREATOR); } @@ -313,10 +309,7 @@ public final class Slice implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeStringArray(mHints); - dest.writeInt(mItems.length); - for (int i = 0; i < mItems.length; i++) { - mItems[i].writeToParcel(dest, flags); - } + dest.writeTypedArray(mItems, flags); mUri.writeToParcel(dest, 0); dest.writeTypedObject(mSpec, flags); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index c41e626444c9..298cec1674b2 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -69,16 +69,19 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.Date; import java.util.Formatter; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.TimeZone; /** * A class providing access to battery usage statistics, including information on @@ -1868,6 +1871,11 @@ public abstract class BatteryStats { @UnsupportedAppUsage public long time; + // Wall clock time of the event, GMT. Unlike `time`, this timestamp is affected + // by changes in the clock setting. When the wall clock is adjusted, BatteryHistory + // records an event of type `CMD_CURRENT_TIME` or `CMD_RESET`. + public long currentTime; + @UnsupportedAppUsage public static final byte CMD_UPDATE = 0; // These can be written as deltas public static final byte CMD_NULL = -1; @@ -2108,9 +2116,6 @@ public abstract class BatteryStats { public int eventCode; public HistoryTag eventTag; - // Only set for CMD_CURRENT_TIME or CMD_RESET, as per System.currentTimeMillis(). - public long currentTime; - // Meta-data when reading. public int numReadInts; @@ -6926,6 +6931,23 @@ public abstract class BatteryStats { } public static class HistoryPrinter { + private static final int FORMAT_LEGACY = 1; + + // This constant MUST be incremented whenever the history dump format changes. + private static final int FORMAT_VERSION = 2; + + private final SimpleDateFormat mHistoryItemTimestampFormat = + new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US); + private final SimpleDateFormat mCurrentTimeEventTimeFormat = + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US); + + // This API is error prone, but we are making an exception here to avoid excessive + // object allocations. + @SuppressWarnings("JavaUtilDate") + private final Date mDate = new Date(); + + private final int mFormatVersion; + int oldState = 0; int oldState2 = 0; int oldLevel = -1; @@ -6939,6 +6961,22 @@ public abstract class BatteryStats { double oldWifiRailChargeMah = -1; long lastTime = -1; + public HistoryPrinter() { + this(TimeZone.getDefault()); + } + + public HistoryPrinter(TimeZone timeZone) { + this(com.android.server.power.optimization.Flags + .extendedBatteryHistoryContinuousCollectionEnabled() + ? FORMAT_VERSION : FORMAT_LEGACY, timeZone); + } + + private HistoryPrinter(int formatVersion, TimeZone timeZone) { + mFormatVersion = formatVersion; + mHistoryItemTimestampFormat.getCalendar().setTimeZone(timeZone); + mCurrentTimeEventTimeFormat.getCalendar().setTimeZone(timeZone); + } + void reset() { oldState = oldState2 = 0; oldLevel = -1; @@ -6966,16 +7004,22 @@ public abstract class BatteryStats { } } + @SuppressWarnings("JavaUtilDate") private String printNextItem(HistoryItem rec, long baseTime, boolean checkin, boolean verbose) { StringBuilder item = new StringBuilder(); if (!checkin) { item.append(" "); - TimeUtils.formatDuration( - rec.time - baseTime, item, TimeUtils.HUNDRED_DAY_FIELD_LEN); - item.append(" ("); - item.append(rec.numReadInts); - item.append(") "); + if (mFormatVersion == FORMAT_LEGACY) { + TimeUtils.formatDuration( + rec.time - baseTime, item, TimeUtils.HUNDRED_DAY_FIELD_LEN); + item.append(" ("); + item.append(rec.numReadInts); + item.append(") "); + } else { + mDate.setTime(rec.currentTime); + item.append(mHistoryItemTimestampFormat.format(mDate)).append(' '); + } } else { item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(','); item.append(HISTORY_DATA); item.append(','); @@ -7007,8 +7051,8 @@ public abstract class BatteryStats { item.append("\n"); } else { item.append(" "); - item.append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", - rec.currentTime).toString()); + mDate.setTime(rec.currentTime); + item.append(mCurrentTimeEventTimeFormat.format(mDate)); item.append("\n"); } } else if (rec.cmd == HistoryItem.CMD_SHUTDOWN) { @@ -7529,11 +7573,31 @@ public abstract class BatteryStats { public static final int DUMP_DEVICE_WIFI_ONLY = 1<<6; private void dumpHistory(PrintWriter pw, int flags, long histStart, boolean checkin) { + final HistoryPrinter hprinter = new HistoryPrinter(); synchronized (this) { - dumpHistoryTagPoolLocked(pw, checkin); + if (!checkin) { + final long historyTotalSize = getHistoryTotalSize(); + final long historyUsedSize = getHistoryUsedSize(); + pw.print("Battery History"); + if (hprinter.mFormatVersion != HistoryPrinter.FORMAT_LEGACY) { + pw.print(" [Format: " + hprinter.mFormatVersion + "]"); + } + pw.print(" ("); + pw.print((100 * historyUsedSize) / historyTotalSize); + pw.print("% used, "); + printSizeValue(pw, historyUsedSize); + pw.print(" used of "); + printSizeValue(pw, historyTotalSize); + pw.print(", "); + pw.print(getHistoryStringPoolSize()); + pw.print(" strings using "); + printSizeValue(pw, getHistoryStringPoolBytes()); + pw.println("):"); + } else { + dumpHistoryTagPoolLocked(pw, checkin); + } } - final HistoryPrinter hprinter = new HistoryPrinter(); long lastTime = -1; long baseTime = -1; boolean printed = false; @@ -7645,20 +7709,6 @@ public abstract class BatteryStats { pw.print("\""); pw.println(); } - } else { - final long historyTotalSize = getHistoryTotalSize(); - final long historyUsedSize = getHistoryUsedSize(); - pw.print("Battery History ("); - pw.print((100 * historyUsedSize) / historyTotalSize); - pw.print("% used, "); - printSizeValue(pw, historyUsedSize); - pw.print(" used of "); - printSizeValue(pw, historyTotalSize); - pw.print(", "); - pw.print(getHistoryStringPoolSize()); - pw.print(" strings using "); - printSizeValue(pw, getHistoryStringPoolBytes()); - pw.println("):"); } } diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 230fa3fec930..d9969d8b9596 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -40,6 +40,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.ravenwood.RavenwoodEnvironment; import dalvik.annotation.optimization.NeverCompile; +import dalvik.annotation.optimization.NeverInline; import java.io.FileDescriptor; import java.lang.annotation.Retention; @@ -237,6 +238,46 @@ public final class MessageQueue { } private final MatchDeliverableMessages mMatchDeliverableMessages = new MatchDeliverableMessages(); + + @NeverInline + private boolean isIdleConcurrent() { + final long now = SystemClock.uptimeMillis(); + + if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) { + return false; + } + + MessageNode msgNode = null; + MessageNode asyncMsgNode = null; + + if (!mPriorityQueue.isEmpty()) { + try { + msgNode = mPriorityQueue.first(); + } catch (NoSuchElementException e) { } + } + + if (!mAsyncPriorityQueue.isEmpty()) { + try { + asyncMsgNode = mAsyncPriorityQueue.first(); + } catch (NoSuchElementException e) { } + } + + if ((msgNode != null && msgNode.getWhen() <= now) + || (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) { + return false; + } + + return true; + } + + @NeverInline + private boolean isIdleLegacy() { + synchronized (this) { + final long now = SystemClock.uptimeMillis(); + return mMessages == null || now < mMessages.when; + } + } + /** * Returns true if the looper has no pending messages which are due to be processed. * @@ -246,38 +287,23 @@ public final class MessageQueue { */ public boolean isIdle() { if (mUseConcurrent) { - final long now = SystemClock.uptimeMillis(); - - if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) { - return false; - } - - MessageNode msgNode = null; - MessageNode asyncMsgNode = null; - - if (!mPriorityQueue.isEmpty()) { - try { - msgNode = mPriorityQueue.first(); - } catch (NoSuchElementException e) { } - } - - if (!mAsyncPriorityQueue.isEmpty()) { - try { - asyncMsgNode = mAsyncPriorityQueue.first(); - } catch (NoSuchElementException e) { } - } + return isIdleConcurrent(); + } else { + return isIdleLegacy(); + } + } - if ((msgNode != null && msgNode.getWhen() <= now) - || (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) { - return false; - } + @NeverInline + private void addIdleHandlerConcurrent(@NonNull IdleHandler handler) { + synchronized (mIdleHandlersLock) { + mIdleHandlers.add(handler); + } + } - return true; - } else { - synchronized (this) { - final long now = SystemClock.uptimeMillis(); - return mMessages == null || now < mMessages.when; - } + @NeverInline + private void addIdleHandlerLegacy(@NonNull IdleHandler handler) { + synchronized (this) { + mIdleHandlers.add(handler); } } @@ -296,13 +322,23 @@ public final class MessageQueue { throw new NullPointerException("Can't add a null IdleHandler"); } if (mUseConcurrent) { - synchronized (mIdleHandlersLock) { - mIdleHandlers.add(handler); - } + addIdleHandlerConcurrent(handler); } else { - synchronized (this) { - mIdleHandlers.add(handler); - } + addIdleHandlerLegacy(handler); + } + } + + @NeverInline + private void removeIdleHandlerConcurrent(@NonNull IdleHandler handler) { + synchronized (mIdleHandlersLock) { + mIdleHandlers.remove(handler); + } + } + + @NeverInline + private void removeIdleHandlerLegacy(@NonNull IdleHandler handler) { + synchronized (this) { + mIdleHandlers.remove(handler); } } @@ -317,13 +353,23 @@ public final class MessageQueue { */ public void removeIdleHandler(@NonNull IdleHandler handler) { if (mUseConcurrent) { - synchronized (mIdleHandlersLock) { - mIdleHandlers.remove(handler); - } + removeIdleHandlerConcurrent(handler); } else { - synchronized (this) { - mIdleHandlers.remove(handler); - } + removeIdleHandlerLegacy(handler); + } + } + + @NeverInline + private boolean isPollingConcurrent() { + // If the loop is quitting then it must not be idling. + // We can assume mPtr != 0 when sQuitting is false. + return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); + } + + @NeverInline + private boolean isPollingLegacy() { + synchronized (this) { + return isPollingLocked(); } } @@ -340,13 +386,9 @@ public final class MessageQueue { */ public boolean isPolling() { if (mUseConcurrent) { - // If the loop is quitting then it must not be idling. - // We can assume mPtr != 0 when sQuitting is false. - return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); + return isPollingConcurrent(); } else { - synchronized (this) { - return isPollingLocked(); - } + return isPollingLegacy(); } } @@ -355,6 +397,23 @@ public final class MessageQueue { // We can assume mPtr != 0 when mQuitting is false. return !mQuitting && nativeIsPolling(mPtr); } + @NeverInline + private void addOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd, + @OnFileDescriptorEventListener.Events int events, + @NonNull OnFileDescriptorEventListener listener) { + synchronized (mFileDescriptorRecordsLock) { + updateOnFileDescriptorEventListenerLocked(fd, events, listener); + } + } + + @NeverInline + private void addOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd, + @OnFileDescriptorEventListener.Events int events, + @NonNull OnFileDescriptorEventListener listener) { + synchronized (this) { + updateOnFileDescriptorEventListenerLocked(fd, events, listener); + } + } /** * Adds a file descriptor listener to receive notification when file descriptor @@ -391,13 +450,23 @@ public final class MessageQueue { } if (mUseConcurrent) { - synchronized (mFileDescriptorRecordsLock) { - updateOnFileDescriptorEventListenerLocked(fd, events, listener); - } + addOnFileDescriptorEventListenerConcurrent(fd, events, listener); } else { - synchronized (this) { - updateOnFileDescriptorEventListenerLocked(fd, events, listener); - } + addOnFileDescriptorEventListenerLegacy(fd, events, listener); + } + } + + @NeverInline + private void removeOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd) { + synchronized (mFileDescriptorRecordsLock) { + updateOnFileDescriptorEventListenerLocked(fd, 0, null); + } + } + + @NeverInline + private void removeOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd) { + synchronized (this) { + updateOnFileDescriptorEventListenerLocked(fd, 0, null); } } @@ -419,13 +488,9 @@ public final class MessageQueue { throw new IllegalArgumentException("fd must not be null"); } if (mUseConcurrent) { - synchronized (mFileDescriptorRecordsLock) { - updateOnFileDescriptorEventListenerLocked(fd, 0, null); - } + removeOnFileDescriptorEventListenerConcurrent(fd); } else { - synchronized (this) { - updateOnFileDescriptorEventListenerLocked(fd, 0, null); - } + removeOnFileDescriptorEventListenerLegacy(fd); } } @@ -732,6 +797,7 @@ public final class MessageQueue { } } + @NeverInline private Message nextConcurrent() { final long ptr = mPtr; if (ptr == 0) { @@ -806,12 +872,8 @@ public final class MessageQueue { } } - @UnsupportedAppUsage - Message next() { - if (mUseConcurrent) { - return nextConcurrent(); - } - + @NeverInline + private Message nextLegacy() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. @@ -929,6 +991,15 @@ public final class MessageQueue { } } + @UnsupportedAppUsage + Message next() { + if (mUseConcurrent) { + return nextConcurrent(); + } else { + return nextLegacy(); + } + } + void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); @@ -966,6 +1037,17 @@ public final class MessageQueue { } } + @NeverInline + private int postSyncBarrierConcurrent() { + return postSyncBarrier(SystemClock.uptimeMillis()); + + } + + @NeverInline + private int postSyncBarrierLegacy() { + return postSyncBarrier(SystemClock.uptimeMillis()); + } + /** * Posts a synchronization barrier to the Looper's message queue. * @@ -992,7 +1074,11 @@ public final class MessageQueue { @UnsupportedAppUsage @TestApi public int postSyncBarrier() { - return postSyncBarrier(SystemClock.uptimeMillis()); + if (mUseConcurrent) { + return postSyncBarrierConcurrent(); + } else { + return postSyncBarrierLegacy(); + } } private int postSyncBarrier(long when) { @@ -1077,48 +1163,35 @@ public final class MessageQueue { } } - /** - * Removes a synchronization barrier. - * - * @param token The synchronization barrier token that was returned by - * {@link #postSyncBarrier}. - * - * @throws IllegalStateException if the barrier was not found. - * - * @hide - */ - @UnsupportedAppUsage - @TestApi - public void removeSyncBarrier(int token) { - // Remove a sync barrier token from the queue. - // If the queue is no longer stalled by a barrier then wake it. - if (mUseConcurrent) { - boolean removed; - MessageNode first; - final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token); - - try { - /* Retain the first element to see if we are currently stuck on a barrier. */ - first = mPriorityQueue.first(); - } catch (NoSuchElementException e) { - /* The queue is empty */ - first = null; - } + @NeverInline + private void removeSyncBarrierConcurrent(int token) { + boolean removed; + MessageNode first; + final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token); - removed = findOrRemoveMessages(null, 0, null, null, 0, matchBarrierToken, true); - if (removed && first != null) { - Message m = first.mMessage; - if (m.target == null && m.arg1 == token) { - /* Wake up next() in case it was sleeping on this barrier. */ - nativeWake(mPtr); - } - } else if (!removed) { - throw new IllegalStateException("The specified message queue synchronization " - + " barrier token has not been posted or has already been removed."); + try { + /* Retain the first element to see if we are currently stuck on a barrier. */ + first = mPriorityQueue.first(); + } catch (NoSuchElementException e) { + /* The queue is empty */ + first = null; + } + + removed = findOrRemoveMessages(null, 0, null, null, 0, matchBarrierToken, true); + if (removed && first != null) { + Message m = first.mMessage; + if (m.target == null && m.arg1 == token) { + /* Wake up next() in case it was sleeping on this barrier. */ + nativeWake(mPtr); } - return; + } else if (!removed) { + throw new IllegalStateException("The specified message queue synchronization " + + " barrier token has not been posted or has already been removed."); } + } + @NeverInline + private void removeSyncBarrierLegacy(int token) { synchronized (this) { Message prev = null; Message p = mMessages; @@ -1154,19 +1227,40 @@ public final class MessageQueue { } } - boolean enqueueMessage(Message msg, long when) { - if (msg.target == null) { - throw new IllegalArgumentException("Message must have a target."); + /** + * Removes a synchronization barrier. + * + * @param token The synchronization barrier token that was returned by + * {@link #postSyncBarrier}. + * + * @throws IllegalStateException if the barrier was not found. + * + * @hide + */ + @UnsupportedAppUsage + @TestApi + public void removeSyncBarrier(int token) { + // Remove a sync barrier token from the queue. + // If the queue is no longer stalled by a barrier then wake it. + if (mUseConcurrent) { + removeSyncBarrierConcurrent(token); + } else { + removeSyncBarrierLegacy(token); } - if (mUseConcurrent) { - if (msg.isInUse()) { - throw new IllegalStateException(msg + " This message is already in use."); - } + } - return enqueueMessageUnchecked(msg, when); + @NeverInline + private boolean enqueueMessageConcurrent(Message msg, long when) { + if (msg.isInUse()) { + throw new IllegalStateException(msg + " This message is already in use."); } + return enqueueMessageUnchecked(msg, when); + } + + @NeverInline + private boolean enqueueMessageLegacy(Message msg, long when) { synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); @@ -1272,6 +1366,18 @@ public final class MessageQueue { return true; } + boolean enqueueMessage(Message msg, long when) { + if (msg.target == null) { + throw new IllegalArgumentException("Message must have a target."); + } + + if (mUseConcurrent) { + return enqueueMessageConcurrent(msg, when); + } else { + return enqueueMessageLegacy(msg, when); + } + } + private Message legacyPeekOrPoll(boolean peek) { synchronized (this) { // Try to retrieve the next message. Return if found. @@ -1432,14 +1538,15 @@ public final class MessageQueue { } private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject = new MatchHandlerWhatAndObject(); - boolean hasMessages(Handler h, int what, Object object) { - if (h == null) { - return false; - } - if (mUseConcurrent) { - return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, - false); - } + + @NeverInline + private boolean hasMessagesConcurrent(Handler h, int what, Object object) { + return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, + false); + } + + @NeverInline + private boolean hasMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; while (p != null) { @@ -1452,6 +1559,17 @@ public final class MessageQueue { } } + boolean hasMessages(Handler h, int what, Object object) { + if (h == null) { + return false; + } + if (mUseConcurrent) { + return hasMessagesConcurrent(h, what, object); + } else { + return hasMessagesLegacy(h, what, object); + } + } + private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare { @Override public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, @@ -1465,15 +1583,15 @@ public final class MessageQueue { } private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals = new MatchHandlerWhatAndObjectEquals(); - boolean hasEqualMessages(Handler h, int what, Object object) { - if (h == null) { - return false; - } - if (mUseConcurrent) { - return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, - false); - } + @NeverInline + private boolean hasEqualMessagesConcurrent(Handler h, int what, Object object) { + return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, + false); + } + + @NeverInline + private boolean hasEqualMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; while (p != null) { @@ -1486,6 +1604,17 @@ public final class MessageQueue { } } + boolean hasEqualMessages(Handler h, int what, Object object) { + if (h == null) { + return false; + } + if (mUseConcurrent) { + return hasEqualMessagesConcurrent(h, what, object); + } else { + return hasEqualMessagesLegacy(h, what, object); + } + } + private static final class MatchHandlerRunnableAndObject extends MessageCompare { @Override public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, @@ -1499,16 +1628,15 @@ public final class MessageQueue { } private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject = new MatchHandlerRunnableAndObject(); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - boolean hasMessages(Handler h, Runnable r, Object object) { - if (h == null) { - return false; - } - if (mUseConcurrent) { - return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, - false); - } + @NeverInline + private boolean hasMessagesConcurrent(Handler h, Runnable r, Object object) { + return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, + false); + } + + @NeverInline + private boolean hasMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; while (p != null) { @@ -1521,6 +1649,18 @@ public final class MessageQueue { } } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + boolean hasMessages(Handler h, Runnable r, Object object) { + if (h == null) { + return false; + } + if (mUseConcurrent) { + return hasMessagesConcurrent(h, r, object); + } else { + return hasMessagesLegacy(h, r, object); + } + } + private static final class MatchHandler extends MessageCompare { @Override public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, @@ -1529,13 +1669,14 @@ public final class MessageQueue { } } private final MatchHandler mMatchHandler = new MatchHandler(); - boolean hasMessages(Handler h) { - if (h == null) { - return false; - } - if (mUseConcurrent) { - return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); - } + + @NeverInline + private boolean hasMessagesConcurrent(Handler h) { + return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); + } + + @NeverInline + private boolean hasMessagesLegacy(Handler h) { synchronized (this) { Message p = mMessages; while (p != null) { @@ -1548,14 +1689,24 @@ public final class MessageQueue { } } - void removeMessages(Handler h, int what, Object object) { + boolean hasMessages(Handler h) { if (h == null) { - return; + return false; } if (mUseConcurrent) { - findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); - return; + return hasMessagesConcurrent(h); + } else { + return hasMessagesLegacy(h); } + } + + @NeverInline + private void removeMessagesConcurrent(Handler h, int what, Object object) { + findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); + } + + @NeverInline + private void removeMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1598,16 +1749,24 @@ public final class MessageQueue { } } - void removeEqualMessages(Handler h, int what, Object object) { + void removeMessages(Handler h, int what, Object object) { if (h == null) { return; } - if (mUseConcurrent) { - findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); - return; + removeMessagesConcurrent(h, what, object); + } else { + removeMessagesLegacy(h, what, object); } + } + @NeverInline + private void removeEqualMessagesConcurrent(Handler h, int what, Object object) { + findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); + } + + @NeverInline + private void removeEqualMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1650,15 +1809,25 @@ public final class MessageQueue { } } - void removeMessages(Handler h, Runnable r, Object object) { - if (h == null || r == null) { + void removeEqualMessages(Handler h, int what, Object object) { + if (h == null) { return; } if (mUseConcurrent) { - findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); - return; + removeEqualMessagesConcurrent(h, what, object); + } else { + removeEqualMessagesLegacy(h, what, object); } + } + + @NeverInline + private void removeMessagesConcurrent(Handler h, Runnable r, Object object) { + findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); + } + + @NeverInline + private void removeMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1701,6 +1870,18 @@ public final class MessageQueue { } } + void removeMessages(Handler h, Runnable r, Object object) { + if (h == null || r == null) { + return; + } + + if (mUseConcurrent) { + removeMessagesConcurrent(h, r, object); + } else { + removeMessagesLegacy(h, r, object); + } + } + private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare { @Override public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, @@ -1714,15 +1895,14 @@ public final class MessageQueue { } private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals = new MatchHandlerRunnableAndObjectEquals(); - void removeEqualMessages(Handler h, Runnable r, Object object) { - if (h == null || r == null) { - return; - } - if (mUseConcurrent) { - findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); - return; - } + @NeverInline + private void removeEqualMessagesConcurrent(Handler h, Runnable r, Object object) { + findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); + } + + @NeverInline + private void removeEqualMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1765,6 +1945,18 @@ public final class MessageQueue { } } + void removeEqualMessages(Handler h, Runnable r, Object object) { + if (h == null || r == null) { + return; + } + + if (mUseConcurrent) { + removeEqualMessagesConcurrent(h, r, object); + } else { + removeEqualMessagesLegacy(h, r, object); + } + } + private static final class MatchHandlerAndObject extends MessageCompare { @Override public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, @@ -1777,15 +1969,14 @@ public final class MessageQueue { } } private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject(); - void removeCallbacksAndMessages(Handler h, Object object) { - if (h == null) { - return; - } - if (mUseConcurrent) { + @NeverInline + private void removeCallbacksAndMessagesConcurrent(Handler h, Object object) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true); - return; - } + } + + @NeverInline + private void removeCallbacksAndMessagesLegacy(Handler h, Object object) { synchronized (this) { Message p = mMessages; @@ -1827,6 +2018,18 @@ public final class MessageQueue { } } + void removeCallbacksAndMessages(Handler h, Object object) { + if (h == null) { + return; + } + + if (mUseConcurrent) { + removeCallbacksAndMessagesConcurrent(h, object); + } else { + removeCallbacksAndMessagesLegacy(h, object); + } + } + private static final class MatchHandlerAndObjectEquals extends MessageCompare { @Override public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, @@ -1840,15 +2043,14 @@ public final class MessageQueue { } private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals = new MatchHandlerAndObjectEquals(); - void removeCallbacksAndEqualMessages(Handler h, Object object) { - if (h == null) { - return; - } - if (mUseConcurrent) { - findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); - return; - } + @NeverInline + void removeCallbacksAndEqualMessagesConcurrent(Handler h, Object object) { + findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); + } + + @NeverInline + void removeCallbacksAndEqualMessagesLegacy(Handler h, Object object) { synchronized (this) { Message p = mMessages; @@ -1890,6 +2092,18 @@ public final class MessageQueue { } } + void removeCallbacksAndEqualMessages(Handler h, Object object) { + if (h == null) { + return; + } + + if (mUseConcurrent) { + removeCallbacksAndEqualMessagesConcurrent(h, object); + } else { + removeCallbacksAndEqualMessagesLegacy(h, object); + } + } + private void removeAllMessagesLocked() { Message p = mMessages; while (p != null) { diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 5ba6553a58c9..0879118ff856 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -3668,6 +3668,7 @@ public final class Parcel { int length = readInt(); if (length >= 0) { + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, length); array = new CharSequence[length]; for (int i = 0 ; i < length ; i++) @@ -3689,6 +3690,7 @@ public final class Parcel { int length = readInt(); if (length >= 0) { + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, length); array = new ArrayList<CharSequence>(length); for (int i = 0 ; i < length ; i++) { @@ -3831,6 +3833,7 @@ public final class Parcel { if (N < 0) { return null; } + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); SparseBooleanArray sa = new SparseBooleanArray(N); readSparseBooleanArrayInternal(sa, N); return sa; @@ -3847,6 +3850,7 @@ public final class Parcel { if (N < 0) { return null; } + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); SparseIntArray sa = new SparseIntArray(N); readSparseIntArrayInternal(sa, N); return sa; @@ -3892,6 +3896,7 @@ public final class Parcel { public final <T> void readTypedList(@NonNull List<T> list, @NonNull Parcelable.Creator<T> c) { int M = list.size(); int N = readInt(); + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); int i = 0; for (; i < M && i < N; i++) { list.set(i, readTypedObject(c)); @@ -4050,6 +4055,7 @@ public final class Parcel { public final void readStringList(@NonNull List<String> list) { int M = list.size(); int N = readInt(); + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); int i = 0; for (; i < M && i < N; i++) { list.set(i, readString()); @@ -4071,6 +4077,7 @@ public final class Parcel { public final void readBinderList(@NonNull List<IBinder> list) { int M = list.size(); int N = readInt(); + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); int i = 0; for (; i < M && i < N; i++) { list.set(i, readStrongBinder()); @@ -4093,6 +4100,7 @@ public final class Parcel { @NonNull Function<IBinder, T> asInterface) { int M = list.size(); int N = readInt(); + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); int i = 0; for (; i < M && i < N; i++) { list.set(i, asInterface.apply(readStrongBinder())); @@ -4159,6 +4167,7 @@ public final class Parcel { list.clear(); return list; } + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, n); final int m = list.size(); int i = 0; @@ -5540,6 +5549,7 @@ public final class Parcel { if (n < 0) { return null; } + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, n); HashMap<K, V> map = new HashMap<>(n); readMapInternal(map, n, loader, clazzKey, clazzValue); return map; @@ -5555,6 +5565,8 @@ public final class Parcel { private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n, @Nullable ClassLoader loader, @Nullable Class<K> clazzKey, @Nullable Class<V> clazzValue) { + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, n); + // TODO: move all reservation of map size here, not all reserves? while (n > 0) { K key = readValue(loader, clazzKey); V value = readValue(loader, clazzValue); @@ -5580,6 +5592,7 @@ public final class Parcel { */ void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) { + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, size); while (size > 0) { String key = readString(); Object value = (lazy) ? readLazyValue(loaderProvider) : readValue( @@ -5625,6 +5638,7 @@ public final class Parcel { if (size < 0) { return null; } + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, size); ArraySet<Object> result = new ArraySet<>(size); for (int i = 0; i < size; i++) { Object value = readValue(loader); @@ -5646,6 +5660,8 @@ public final class Parcel { */ private <T> void readListInternal(@NonNull List<? super T> outVal, int n, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, n); + // TODO: move all size reservations here, instead of code that calls this. Not all reserves. while (n > 0) { T value = readValue(loader, clazz); //Log.d(TAG, "Unmarshalling value=" + value); @@ -5665,6 +5681,7 @@ public final class Parcel { if (n < 0) { return null; } + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, n); ArrayList<T> l = new ArrayList<>(n); readListInternal(l, n, loader, clazz); return l; @@ -5707,6 +5724,7 @@ public final class Parcel { */ private void readSparseArrayInternal(@NonNull SparseArray outVal, int N, @Nullable ClassLoader loader) { + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); while (N > 0) { int key = readInt(); Object value = readValue(loader); @@ -5725,6 +5743,7 @@ public final class Parcel { if (n < 0) { return null; } + ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, n); SparseArray<T> outVal = new SparseArray<>(n); while (n > 0) { diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index bdf8d23438df..343d7527ea98 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1716,20 +1716,14 @@ public final class PermissionManager { private static int checkPermissionUncached(@Nullable String permission, int pid, int uid, int deviceId) { + final int appId = UserHandle.getAppId(uid); + if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) { + return PackageManager.PERMISSION_GRANTED; + } final IActivityManager am = ActivityManager.getService(); if (am == null) { - // Well this is super awkward; we somehow don't have an active ActivityManager - // instance. If we're testing a root or system UID, then they totally have whatever - // permission this is. - final int appId = UserHandle.getAppId(uid); - if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) { - if (sShouldWarnMissingActivityManager) { - Slog.w(LOG_TAG, "Missing ActivityManager; assuming " + uid + " holds " - + permission); - sShouldWarnMissingActivityManager = false; - } - return PackageManager.PERMISSION_GRANTED; - } + // We don't have an active ActivityManager instance and the calling UID is not root or + // system, so we don't grant this permission. Slog.w(LOG_TAG, "Missing ActivityManager; assuming " + uid + " does not hold " + permission); return PackageManager.PERMISSION_DENIED; diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index e8b32ce5e314..cc19e7cab537 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -1009,7 +1009,7 @@ public class TelephonyCallback { */ public interface CellInfoListener { /** - * Callback invoked when a observed cell info has changed or new cells have been added + * Callback invoked when an observed cell info has changed or new cells have been added * or removed on the registered subscription. * Note, the registration subscription ID s from {@link TelephonyManager} object * which registers TelephonyCallback by diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index abd93cfaf179..b38feeef290b 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -1,8 +1,10 @@ +# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto +# proto-message: flag_declarations +# Project link: http://gantry/projects/android_platform_windowing_sdk + package: "com.android.window.flags" container: "system" -# Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes - flag { namespace: "windowing_sdk" name: "activity_embedding_overlay_presentation_flag" diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index e2005d7cdedf..ee897cd37cd3 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -45,6 +45,8 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor private boolean mNextItemReady; private boolean mTimeInitialized; private boolean mClosed; + private long mBaseMonotonicTime; + private long mBaseTimeUtc; public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs, long endTimeMs) { @@ -84,24 +86,25 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor } if (!mTimeInitialized) { - mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p); + mBaseMonotonicTime = mBatteryStatsHistory.getHistoryBufferStartTime(p); + mHistoryItem.time = mBaseMonotonicTime; mTimeInitialized = true; } - final long lastMonotonicTimeMs = mHistoryItem.time; - final long lastWalltimeMs = mHistoryItem.currentTime; try { readHistoryDelta(p, mHistoryItem); } catch (Throwable t) { Slog.wtf(TAG, "Corrupted battery history", t); break; } - if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME - && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET - && lastWalltimeMs != 0) { - mHistoryItem.currentTime = - lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs); + + if (mHistoryItem.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME + || mHistoryItem.cmd == BatteryStats.HistoryItem.CMD_RESET) { + mBaseTimeUtc = mHistoryItem.currentTime - (mHistoryItem.time - mBaseMonotonicTime); } + + mHistoryItem.currentTime = mBaseTimeUtc + (mHistoryItem.time - mBaseMonotonicTime); + if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) { break; } diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 240be3fe5534..c105a6098870 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -704,7 +704,8 @@ static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jl jint flags) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); // Prevent private flags from being used from Java. - event->setFlags(flags & ~AMOTION_EVENT_PRIVATE_FLAG_MASK); + const int32_t privateFlags = event->getFlags() & AMOTION_EVENT_PRIVATE_FLAG_MASK; + event->setFlags((flags & ~AMOTION_EVENT_PRIVATE_FLAG_MASK) | privateFlags); } static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) { diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index f40cfd9f8e51..3108f1f2c7c5 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -314,8 +314,9 @@ static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zip when, uncompLen, crc); } - ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly " - "from apk.\n", + ALOGE("extractNativeLibs=false library '%s' is not PAGE(%zu)-" + "aligned within apk (APK alignment, not ELF alignment) -" + "will not be able to open it directly from apk.\n", fileName, kPageSize); return INSTALL_FAILED_INVALID_APK; } diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java index 335791c031b4..5fff0b8d0849 100644 --- a/core/tests/coretests/src/android/os/BinderProxyTest.java +++ b/core/tests/coretests/src/android/os/BinderProxyTest.java @@ -138,7 +138,7 @@ public class BinderProxyTest { new Intent(mContext, BinderProxyService.class), connection, Context.BIND_AUTO_CREATE); - if (!bindLatch.await(500, TimeUnit.MILLISECONDS)) { + if (!bindLatch.await(1000, TimeUnit.MILLISECONDS)) { fail( "Timed out while binding service: " + BinderProxyService.class.getSimpleName()); diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java deleted file mode 100644 index 30f6636766d5..000000000000 --- a/core/tests/coretests/src/android/os/MessageQueueTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -import android.platform.test.ravenwood.RavenwoodRule; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.MediumTest; -import androidx.test.filters.Suppress; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class MessageQueueTest { - -} diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java index d0f9a38720bf..419c05f5b8c1 100644 --- a/core/tests/coretests/src/android/view/MotionEventTest.java +++ b/core/tests/coretests/src/android/view/MotionEventTest.java @@ -49,9 +49,14 @@ public class MotionEventTest { private static final int ID_SOURCE_MASK = 0x3 << 30; private PointerCoords pointerCoords(float x, float y) { + return pointerCoords(x, y, 0f /*orientation*/); + } + + private PointerCoords pointerCoords(float x, float y, float orientation) { final var coords = new PointerCoords(); coords.x = x; coords.y = y; + coords.orientation = orientation; return coords; } @@ -295,4 +300,24 @@ public class MotionEventTest { // Expected } } + + @Test + public void testAxesAreNotAffectedByFlagChanges() { + final int pointerCount = 1; + final var properties = new PointerProperties[]{fingerProperties(0)}; + final var coords = new PointerCoords[]{pointerCoords(20, 60, 0.1234f)}; + + final MotionEvent event = MotionEvent.obtain(0 /* downTime */, + 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords, + 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */, + 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN, + 0 /* flags */); + + // Mark the event as tainted to update the MotionEvent flags. + event.setTainted(true); + + assertEquals(20f, event.getX(), 0.0001); + assertEquals(60f, event.getY(), 0.0001); + assertEquals(0.1234f, event.getOrientation(), 0.0001); + } } diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index bbdcbc9147e4..688bf83224aa 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -1,4 +1,6 @@ # proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto +# proto-message: flag_declarations +# Project link: http://gantry/projects/android_platform_multitasking package: "com.android.wm.shell" container: "system" diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 239acd37f286..ab2e552c7a3d 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -49,7 +49,9 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito +import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit @@ -175,6 +177,137 @@ class BubbleStackViewTest { } @Test + fun expandStack_imeHidden() { + val bubble = createAndInflateBubble() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble) + } + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + assertThat(bubbleStackView.bubbleCount).isEqualTo(1) + + positioner.setImeVisible(false, 0) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // simulate a request from the bubble data listener to expand the stack + bubbleStackView.isExpanded = true + verify(sysuiProxy).onStackExpandChanged(true) + shellExecutor.flushAll() + } + + assertThat(bubbleStackViewManager.onImeHidden).isNull() + } + + @Test + fun collapseStack_imeHidden() { + val bubble = createAndInflateBubble() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble) + } + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + assertThat(bubbleStackView.bubbleCount).isEqualTo(1) + + positioner.setImeVisible(false, 0) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // simulate a request from the bubble data listener to expand the stack + bubbleStackView.isExpanded = true + verify(sysuiProxy).onStackExpandChanged(true) + shellExecutor.flushAll() + } + + assertThat(bubbleStackViewManager.onImeHidden).isNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // simulate a request from the bubble data listener to collapse the stack + bubbleStackView.isExpanded = false + verify(sysuiProxy).onStackExpandChanged(false) + shellExecutor.flushAll() + } + + assertThat(bubbleStackViewManager.onImeHidden).isNull() + } + + @Test + fun expandStack_waitsForIme() { + val bubble = createAndInflateBubble() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble) + } + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + assertThat(bubbleStackView.bubbleCount).isEqualTo(1) + + positioner.setImeVisible(true, 100) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // simulate a request from the bubble data listener to expand the stack + bubbleStackView.isExpanded = true + } + + val onImeHidden = bubbleStackViewManager.onImeHidden + assertThat(onImeHidden).isNotNull() + verify(sysuiProxy, never()).onStackExpandChanged(any()) + positioner.setImeVisible(false, 0) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + onImeHidden!!.run() + verify(sysuiProxy).onStackExpandChanged(true) + shellExecutor.flushAll() + } + } + + @Test + fun collapseStack_waitsForIme() { + val bubble = createAndInflateBubble() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble) + } + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + assertThat(bubbleStackView.bubbleCount).isEqualTo(1) + + positioner.setImeVisible(true, 100) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // simulate a request from the bubble data listener to expand the stack + bubbleStackView.isExpanded = true + } + + var onImeHidden = bubbleStackViewManager.onImeHidden + assertThat(onImeHidden).isNotNull() + verify(sysuiProxy, never()).onStackExpandChanged(any()) + positioner.setImeVisible(false, 0) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + onImeHidden!!.run() + verify(sysuiProxy).onStackExpandChanged(true) + shellExecutor.flushAll() + } + + bubbleStackViewManager.onImeHidden = null + positioner.setImeVisible(true, 100) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // simulate a request from the bubble data listener to collapse the stack + bubbleStackView.isExpanded = false + } + + onImeHidden = bubbleStackViewManager.onImeHidden + assertThat(onImeHidden).isNotNull() + verify(sysuiProxy, never()).onStackExpandChanged(false) + positioner.setImeVisible(false, 0) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + onImeHidden!!.run() + verify(sysuiProxy).onStackExpandChanged(false) + shellExecutor.flushAll() + } + } + + @Test fun tapDifferentBubble_shouldReorder() { val bubble1 = createAndInflateChatBubble(key = "bubble1") val bubble2 = createAndInflateChatBubble(key = "bubble2") @@ -418,6 +551,7 @@ class BubbleStackViewTest { } private class FakeBubbleStackViewManager : BubbleStackViewManager { + var onImeHidden: Runnable? = null override fun onAllBubblesAnimatedOut() {} @@ -425,6 +559,8 @@ class BubbleStackViewTest { override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {} - override fun hideCurrentInputMethod() {} + override fun hideCurrentInputMethod(onImeHidden: Runnable?) { + this.onImeHidden = onImeHidden + } } } diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml index fcf74e3c1936..87c520ca1b51 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml @@ -48,7 +48,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:maxWidth="130dp" - android:textAppearance="@android:style/TextAppearance.Material.Title" android:textSize="14sp" android:textFontWeight="500" android:lineHeight="20sp" @@ -56,9 +55,7 @@ android:layout_weight="1" android:layout_marginStart="8dp" android:singleLine="true" - android:ellipsize="none" - android:requiresFadingEdge="horizontal" - android:fadingEdgeLength="28dp" + android:ellipsize="end" android:clickable="false" android:focusable="false" tools:text="Gmail"/> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 8efeecb56dbf..08e36927d978 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -215,6 +215,8 @@ public class BubbleController implements ConfigurationChangeListener, private final BubblePositioner mBubblePositioner; private Bubbles.SysuiProxy mSysuiProxy; + @Nullable private Runnable mOnImeHidden; + // Tracks the id of the current (foreground) user. private int mCurrentUserId; // Current profiles of the user (e.g. user with a workprofile) @@ -615,7 +617,8 @@ public class BubbleController implements ConfigurationChangeListener, /** * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal. */ - void hideCurrentInputMethod() { + void hideCurrentInputMethod(@Nullable Runnable onImeHidden) { + mOnImeHidden = onImeHidden; mBubblePositioner.setImeVisible(false /* visible */, 0 /* height */); int displayId = mWindowManager.getDefaultDisplay().getDisplayId(); try { @@ -2267,7 +2270,7 @@ public class BubbleController implements ConfigurationChangeListener, if (mLayerView != null && mLayerView.isExpanded()) { if (mBubblePositioner.isImeVisible()) { // If we're collapsing, hide the IME - hideCurrentInputMethod(); + hideCurrentInputMethod(null); } mLayerView.collapse(); } @@ -2552,6 +2555,10 @@ public class BubbleController implements ConfigurationChangeListener, mBubblePositioner.setImeVisible(imeVisible, totalImeHeight); if (mStackView != null) { mStackView.setImeVisible(imeVisible); + if (!imeVisible && mOnImeHidden != null) { + mOnImeHidden.run(); + mOnImeHidden = null; + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt index 6423eed59165..a02623138f1e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt @@ -82,7 +82,7 @@ interface BubbleExpandedViewManager { override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar override fun hideCurrentInputMethod() { - controller.hideCurrentInputMethod() + controller.hideCurrentInputMethod(null) } override fun updateBubbleBarLocation( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 249a218d5e56..979d958e3c5f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -280,6 +280,7 @@ public class BubbleStackView extends FrameLayout private int mCornerRadius; @Nullable private BubbleViewProvider mExpandedBubble; private boolean mIsExpanded; + private boolean mIsImeVisible = false; /** Whether the stack is currently on the left side of the screen, or animating there. */ private boolean mStackOnLeftOrWillBe = true; @@ -2233,29 +2234,40 @@ public class BubbleStackView extends FrameLayout boolean wasExpanded = mIsExpanded; - hideCurrentInputMethod(); + // Do the actual expansion/collapse after the IME is hidden if it's currently visible in + // order to avoid flickers + Runnable onImeHidden = () -> { + mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand); - mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand); + if (wasExpanded) { + stopMonitoringSwipeUpGesture(); + animateCollapse(); + showManageMenu(false); + logBubbleEvent(mExpandedBubble, + FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); + } else { + animateExpansion(); + // TODO: move next line to BubbleData + logBubbleEvent(mExpandedBubble, + FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); + logBubbleEvent(mExpandedBubble, + FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); + mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> { + if (!notifPanelExpanded && mIsExpanded) { + startMonitoringSwipeUpGesture(); + } + }); + } + notifyExpansionChanged(mExpandedBubble, mIsExpanded); + announceExpandForAccessibility(mExpandedBubble, mIsExpanded); + }; - if (wasExpanded) { - stopMonitoringSwipeUpGesture(); - animateCollapse(); - showManageMenu(false); - logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); + if (mPositioner.isImeVisible()) { + hideCurrentInputMethod(onImeHidden); } else { - animateExpansion(); - // TODO: move next line to BubbleData - logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); - logBubbleEvent(mExpandedBubble, - FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); - mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> { - if (!notifPanelExpanded && mIsExpanded) { - startMonitoringSwipeUpGesture(); - } - }); + // the IME is already hidden, so run the runnable immediately + onImeHidden.run(); } - notifyExpansionChanged(mExpandedBubble, mIsExpanded); - announceExpandForAccessibility(mExpandedBubble, mIsExpanded); } /** @@ -2373,7 +2385,17 @@ public class BubbleStackView extends FrameLayout * not. */ void hideCurrentInputMethod() { - mManager.hideCurrentInputMethod(); + mManager.hideCurrentInputMethod(null); + } + + /** + * Hides the IME similar to {@link #hideCurrentInputMethod()} but also runs {@code onImeHidden} + * after after the IME is hidden. + * + * @see #hideCurrentInputMethod() + */ + void hideCurrentInputMethod(Runnable onImeHidden) { + mManager.hideCurrentInputMethod(onImeHidden); } /** Set the stack position to whatever the positioner says. */ @@ -2865,6 +2887,10 @@ public class BubbleStackView extends FrameLayout * and clip the expanded view. */ public void setImeVisible(boolean visible) { + if (mIsImeVisible == visible) { + return; + } + mIsImeVisible = visible; if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) { // This will update the animation so the bubbles move to position for the IME mExpandedAnimationController.expandFromStack(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt index fb597a05663b..8b901a1273f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt @@ -34,7 +34,7 @@ interface BubbleStackViewManager { fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) /** Requests to hide the current input method. */ - fun hideCurrentInputMethod() + fun hideCurrentInputMethod(onImeHidden: Runnable?) companion object { @@ -52,8 +52,8 @@ interface BubbleStackViewManager { controller.isNotificationPanelExpanded(callback) } - override fun hideCurrentInputMethod() { - controller.hideCurrentInputMethod() + override fun hideCurrentInputMethod(onImeHidden: Runnable?) { + controller.hideCurrentInputMethod(onImeHidden) } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java new file mode 100644 index 000000000000..3f76fd0220ff --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common.split; + +import static android.view.WindowManager.DOCKED_BOTTOM; +import static android.view.WindowManager.DOCKED_INVALID; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.DOCKED_TOP; + +import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; +import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_DISMISSING; +import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX; +import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_NONE; +import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR; +import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR; + +import android.graphics.Point; +import android.graphics.Rect; +import android.view.SurfaceControl; +import android.view.WindowManager; + +/** + * This class governs how and when parallax and dimming effects are applied to task surfaces, + * usually when the divider is being moved around by the user (or during an animation). + */ +class ResizingEffectPolicy { + private final SplitLayout mSplitLayout; + /** The parallax algorithm we are currently using. */ + private final int mParallaxType; + + int mShrinkSide = DOCKED_INVALID; + + // The current dismissing side. + int mDismissingSide = DOCKED_INVALID; + + /** + * A {@link Point} that stores a single x and y value, representing the parallax translation + * we use on the app that the divider is moving toward. The app is either shrinking in size or + * getting pushed off the screen. + */ + final Point mRetreatingSideParallax = new Point(); + /** + * A {@link Point} that stores a single x and y value, representing the parallax translation + * we use on the app that the divider is moving away from. The app is either growing in size or + * getting pulled onto the screen. + */ + final Point mAdvancingSideParallax = new Point(); + + // The dimming value to hint the dismissing side and progress. + float mDismissingDimValue = 0.0f; + + /** + * Content bounds for the app that the divider is moving toward. This is the content that is + * currently drawn at the start of the divider movement. It stays unchanged throughout the + * divider's movement. + */ + final Rect mRetreatingContent = new Rect(); + /** + * Surface bounds for the app that the divider is moving toward. This is the "canvas" on + * which an app could potentially be drawn. It changes on every frame as the divider moves + * around. + */ + final Rect mRetreatingSurface = new Rect(); + /** + * Content bounds for the app that the divider is moving away from. This is the content that + * is currently drawn at the start of the divider movement. It stays unchanged throughout + * the divider's movement. + */ + final Rect mAdvancingContent = new Rect(); + /** + * Surface bounds for the app that the divider is moving away from. This is the "canvas" on + * which an app could potentially be drawn. It changes on every frame as the divider moves + * around. + */ + final Rect mAdvancingSurface = new Rect(); + + final Rect mTempRect = new Rect(); + final Rect mTempRect2 = new Rect(); + + ResizingEffectPolicy(int parallaxType, SplitLayout splitLayout) { + mParallaxType = parallaxType; + mSplitLayout = splitLayout; + } + + /** + * Calculates the desired parallax values and stores them in {@link #mRetreatingSideParallax} + * and {@link #mAdvancingSideParallax}. These values will be then be applied in + * {@link #adjustRootSurface}. + * + * @param position The divider's position on the screen (x-coordinate in left-right split, + * y-coordinate in top-bottom split). + */ + void applyDividerPosition( + int position, boolean isLeftRightSplit, DividerSnapAlgorithm snapAlgorithm) { + mDismissingSide = DOCKED_INVALID; + mRetreatingSideParallax.set(0, 0); + mAdvancingSideParallax.set(0, 0); + mDismissingDimValue = 0; + Rect displayBounds = mSplitLayout.getRootBounds(); + + int totalDismissingDistance = 0; + if (position < snapAlgorithm.getFirstSplitTarget().position) { + mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP; + totalDismissingDistance = snapAlgorithm.getDismissStartTarget().position + - snapAlgorithm.getFirstSplitTarget().position; + } else if (position > snapAlgorithm.getLastSplitTarget().position) { + mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM; + totalDismissingDistance = snapAlgorithm.getLastSplitTarget().position + - snapAlgorithm.getDismissEndTarget().position; + } + + final boolean topLeftShrink = isLeftRightSplit + ? position < mSplitLayout.getTopLeftContentBounds().right + : position < mSplitLayout.getTopLeftContentBounds().bottom; + if (topLeftShrink) { + mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP; + mRetreatingContent.set(mSplitLayout.getTopLeftContentBounds()); + mRetreatingSurface.set(mSplitLayout.getTopLeftBounds()); + mAdvancingContent.set(mSplitLayout.getBottomRightContentBounds()); + mAdvancingSurface.set(mSplitLayout.getBottomRightBounds()); + } else { + mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM; + mRetreatingContent.set(mSplitLayout.getBottomRightContentBounds()); + mRetreatingSurface.set(mSplitLayout.getBottomRightBounds()); + mAdvancingContent.set(mSplitLayout.getTopLeftContentBounds()); + mAdvancingSurface.set(mSplitLayout.getTopLeftBounds()); + } + + if (mDismissingSide != DOCKED_INVALID) { + float fraction = + Math.max(0, Math.min(snapAlgorithm.calculateDismissingFraction(position), 1f)); + mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction); + if (mParallaxType == PARALLAX_DISMISSING) { + fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide); + if (isLeftRightSplit) { + mRetreatingSideParallax.x = (int) (fraction * totalDismissingDistance); + } else { + mRetreatingSideParallax.y = (int) (fraction * totalDismissingDistance); + } + } + } + + if (mParallaxType == PARALLAX_ALIGN_CENTER) { + if (isLeftRightSplit) { + mRetreatingSideParallax.x = + (mRetreatingSurface.width() - mRetreatingContent.width()) / 2; + } else { + mRetreatingSideParallax.y = + (mRetreatingSurface.height() - mRetreatingContent.height()) / 2; + } + } else if (mParallaxType == PARALLAX_FLEX) { + // Whether an app is getting pushed offscreen by the divider. + boolean isRetreatingOffscreen = !displayBounds.contains(mRetreatingSurface); + // Whether an app was getting pulled onscreen at the beginning of the drag. + boolean advancingSideStartedOffscreen = !displayBounds.contains(mAdvancingContent); + + // The simpler case when an app gets pushed offscreen (e.g. 50:50 -> 90:10) + if (isRetreatingOffscreen && !advancingSideStartedOffscreen) { + // On the left side, we use parallax to simulate the contents sticking to the + // divider. This is because surfaces naturally expand to the bottom and right, + // so when a surface's area expands, the contents stick to the left. This is + // correct behavior on the right-side surface, but not the left. + if (topLeftShrink) { + if (isLeftRightSplit) { + mRetreatingSideParallax.x = + mRetreatingSurface.width() - mRetreatingContent.width(); + } else { + mRetreatingSideParallax.y = + mRetreatingSurface.height() - mRetreatingContent.height(); + } + } + // All other cases (e.g. 10:90 -> 50:50, 10:90 -> 90:10, 10:90 -> dismiss) + } else { + mTempRect.set(mRetreatingSurface); + Point rootOffset = new Point(); + // 10:90 -> 50:50, 10:90, or dismiss right + if (advancingSideStartedOffscreen) { + // We have to handle a complicated case here to keep the parallax smooth. + // When the divider crosses the 50% mark, the retreating-side app surface + // will start expanding offscreen. This is expected and unavoidable, but + // makes the parallax look disjointed. In order to preserve the illusion, + // we add another offset (rootOffset) to simulate the surface staying + // onscreen. + mTempRect.intersect(displayBounds); + if (mRetreatingSurface.left < displayBounds.left) { + rootOffset.x = displayBounds.left - mRetreatingSurface.left; + } + if (mRetreatingSurface.top < displayBounds.top) { + rootOffset.y = displayBounds.top - mRetreatingSurface.top; + } + + // On the left side, we again have to simulate the contents sticking to the + // divider. + if (!topLeftShrink) { + if (isLeftRightSplit) { + mAdvancingSideParallax.x = + mAdvancingSurface.width() - mAdvancingContent.width(); + } else { + mAdvancingSideParallax.y = + mAdvancingSurface.height() - mAdvancingContent.height(); + } + } + } + + // In all these cases, the shrinking app also receives a center parallax. + if (isLeftRightSplit) { + mRetreatingSideParallax.x = rootOffset.x + + ((mTempRect.width() - mRetreatingContent.width()) / 2); + } else { + mRetreatingSideParallax.y = rootOffset.y + + ((mTempRect.height() - mRetreatingContent.height()) / 2); + } + } + } + } + + /** + * @return for a specified {@code fraction}, this returns an adjusted value that simulates a + * slowing down parallax effect + */ + private float calculateParallaxDismissingFraction(float fraction, int dockSide) { + float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; + + // Less parallax at the top, just because. + if (dockSide == WindowManager.DOCKED_TOP) { + result /= 2f; + } + return result; + } + + /** Applies the calculated parallax and dimming values to task surfaces. */ + void adjustRootSurface(SurfaceControl.Transaction t, + SurfaceControl leash1, SurfaceControl leash2) { + SurfaceControl retreatingLeash = null; + SurfaceControl advancingLeash = null; + + if (mParallaxType == PARALLAX_DISMISSING) { + switch (mDismissingSide) { + case DOCKED_TOP: + case DOCKED_LEFT: + retreatingLeash = leash1; + mTempRect.set(mSplitLayout.getTopLeftBounds()); + advancingLeash = leash2; + mTempRect2.set(mSplitLayout.getBottomRightBounds()); + break; + case DOCKED_BOTTOM: + case DOCKED_RIGHT: + retreatingLeash = leash2; + mTempRect.set(mSplitLayout.getBottomRightBounds()); + advancingLeash = leash1; + mTempRect2.set(mSplitLayout.getTopLeftBounds()); + break; + } + } else if (mParallaxType == PARALLAX_ALIGN_CENTER || mParallaxType == PARALLAX_FLEX) { + switch (mShrinkSide) { + case DOCKED_TOP: + case DOCKED_LEFT: + retreatingLeash = leash1; + mTempRect.set(mSplitLayout.getTopLeftBounds()); + advancingLeash = leash2; + mTempRect2.set(mSplitLayout.getBottomRightBounds()); + break; + case DOCKED_BOTTOM: + case DOCKED_RIGHT: + retreatingLeash = leash2; + mTempRect.set(mSplitLayout.getBottomRightBounds()); + advancingLeash = leash1; + mTempRect2.set(mSplitLayout.getTopLeftBounds()); + break; + } + } + if (mParallaxType != PARALLAX_NONE + && retreatingLeash != null && advancingLeash != null) { + t.setPosition(retreatingLeash, mTempRect.left + mRetreatingSideParallax.x, + mTempRect.top + mRetreatingSideParallax.y); + // Transform the screen-based split bounds to surface-based crop bounds. + mTempRect.offsetTo(-mRetreatingSideParallax.x, -mRetreatingSideParallax.y); + t.setWindowCrop(retreatingLeash, mTempRect); + + t.setPosition(advancingLeash, mTempRect2.left + mAdvancingSideParallax.x, + mTempRect2.top + mAdvancingSideParallax.y); + // Transform the screen-based split bounds to surface-based crop bounds. + mTempRect2.offsetTo(-mAdvancingSideParallax.x, -mAdvancingSideParallax.y); + t.setWindowCrop(advancingLeash, mTempRect2); + } + } + + void adjustDimSurface(SurfaceControl.Transaction t, + SurfaceControl dimLayer1, SurfaceControl dimLayer2) { + SurfaceControl targetDimLayer; + switch (mDismissingSide) { + case DOCKED_TOP: + case DOCKED_LEFT: + targetDimLayer = dimLayer1; + break; + case DOCKED_BOTTOM: + case DOCKED_RIGHT: + targetDimLayer = dimLayer2; + break; + case DOCKED_INVALID: + default: + t.setAlpha(dimLayer1, 0).hide(dimLayer1); + t.setAlpha(dimLayer2, 0).hide(dimLayer2); + return; + } + t.setAlpha(targetDimLayer, mDismissingDimValue) + .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 9fb36b36ff29..a73d43c54064 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -227,10 +227,37 @@ public class SplitDecorManager extends WindowlessWindowManager { mInstantaneousBounds.setEmpty(); } - /** Showing resizing hint. */ + /** + * Called on every frame when an app is getting resized, and controls the showing & hiding of + * the app veil. IMPORTANT: There is one SplitDecorManager for each task, so if two tasks are + * getting resized simultaneously, this method is called in parallel on the other + * SplitDecorManager too. In general, we want to hide the app behind a veil when: + * a) the app is stretching past its original bounds (because app content layout doesn't + * update mid-stretch). + * b) the app is resizing down from fullscreen (because there is no parallax effect that + * makes every app look good in this scenario). + * In the world of flexible split, where apps can go offscreen, there is an exception to this: + * - We do NOT hide the app when it is going offscreen, even though it is technically + * getting larger and would qualify for condition (a). Instead, we use parallax to give + * the illusion that the app is getting pushed offscreen by the divider. + * + * @param resizingTask The task that is getting resized. + * @param newBounds The bounds that that we are updating this surface to. This can be an + * instantaneous bounds, just for a frame, during a drag or animation. + * @param sideBounds The bounds of the OPPOSITE task in the split layout. This is used just for + * reference/calculation, the surface of the other app won't be set here. + * @param displayBounds The bounds of the entire display. + * @param t The transaction on which these changes will be bundled. + * @param offsetX The x-translation applied to the task surface for parallax. Will be used to + * position the task screenshot and/or icon veil. + * @param offsetY The x-translation applied to the task surface for parallax. Will be used to + * position the task screenshot and/or icon veil. + * @param immediately {@code true} if the veil should transition in/out instantly, with no + * animation. + */ public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, - Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, - boolean immediately) { + Rect sideBounds, Rect displayBounds, SurfaceControl.Transaction t, int offsetX, + int offsetY, boolean immediately) { if (mVeilIconView == null) { return; } @@ -252,7 +279,10 @@ public class SplitDecorManager extends WindowlessWindowManager { final boolean isStretchingPastOriginalBounds = newBounds.width() > mOldMainBounds.width() || newBounds.height() > mOldMainBounds.height(); - final boolean showVeil = isResizingDownFromFullscreen || isStretchingPastOriginalBounds; + final boolean isFullyOnscreen = displayBounds.contains(newBounds); + boolean showVeil = isFullyOnscreen + && (isResizingDownFromFullscreen || isStretchingPastOriginalBounds); + final boolean update = showVeil != mShown; if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) { // If we need to animate and animator still running, cancel it before we ensure both 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 4bcec702281d..fc274d6e7174 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 @@ -18,19 +18,14 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; -import static android.view.WindowManager.DOCKED_BOTTOM; -import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; -import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE; -import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR; import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_IN; import static com.android.wm.shell.shared.animation.Interpolators.LINEAR; -import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45; @@ -52,7 +47,6 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; -import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.util.Log; @@ -103,9 +97,17 @@ import java.util.function.Consumer; */ public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener { private static final String TAG = "SplitLayout"; + /** No parallax effect when the user is dragging the divider */ public static final int PARALLAX_NONE = 0; public static final int PARALLAX_DISMISSING = 1; + /** Parallax effect (center-aligned) when the user is dragging the divider */ public static final int PARALLAX_ALIGN_CENTER = 2; + /** + * A custom parallax effect for flexible split. When an app is being pushed/pulled offscreen, + * we use a specific parallax to give the impression that it is stuck to the divider. + * Otherwise, we fall back to PARALLAX_ALIGN_CENTER behavior. + */ + public static final int PARALLAX_FLEX = 3; public static final int FLING_RESIZE_DURATION = 250; private static final int FLING_ENTER_DURATION = 450; @@ -146,6 +148,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private int mDividerSize; private final Rect mTempRect = new Rect(); + private final Rect mTempRect2 = new Rect(); private final Rect mRootBounds = new Rect(); private final Rect mDividerBounds = new Rect(); /** @@ -219,7 +222,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange parentContainerCallbacks); mTaskOrganizer = taskOrganizer; mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId()); - mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType); + mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType, this); mSplitState = splitState; final Resources res = mContext.getResources(); @@ -580,7 +583,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */); DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */); if (setEffectBounds) { - mSurfaceEffectPolicy.applyDividerPosition(position, mIsLeftRightSplit); + mSurfaceEffectPolicy.applyDividerPosition( + position, mIsLeftRightSplit, mDividerSnapAlgorithm); } } @@ -710,8 +714,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange */ void updateDividerBounds(int position, boolean shouldUseParallaxEffect) { updateBounds(position); - mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x, - mSurfaceEffectPolicy.mParallaxOffset.y, shouldUseParallaxEffect); + mSplitLayoutHandler.onLayoutSizeChanging(this, + mSurfaceEffectPolicy.mRetreatingSideParallax.x, + mSurfaceEffectPolicy.mRetreatingSideParallax.y, shouldUseParallaxEffect); } void setDividerPosition(int position, boolean applyLayoutChange) { @@ -1361,169 +1366,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange int getSplitItemPosition(WindowContainerToken token); } - /** - * Calculates and applies proper dismissing parallax offset and dimming value to hint users - * dismissing gesture. - */ - private class ResizingEffectPolicy { - /** Indicates whether to offset splitting bounds to hint dismissing progress or not. */ - private final int mParallaxType; - - int mShrinkSide = DOCKED_INVALID; - - // The current dismissing side. - int mDismissingSide = DOCKED_INVALID; - - // The parallax offset to hint the dismissing side and progress. - final Point mParallaxOffset = new Point(); - - // The dimming value to hint the dismissing side and progress. - float mDismissingDimValue = 0.0f; - final Rect mContentBounds = new Rect(); - final Rect mSurfaceBounds = new Rect(); - - ResizingEffectPolicy(int parallaxType) { - mParallaxType = parallaxType; - } - - /** - * Applies a parallax to the task to hint dismissing progress. - * - * @param position the split position to apply dismissing parallax effect - * @param isLeftRightSplit indicates whether it's splitting horizontally or vertically - */ - void applyDividerPosition(int position, boolean isLeftRightSplit) { - mDismissingSide = DOCKED_INVALID; - mParallaxOffset.set(0, 0); - mDismissingDimValue = 0; - - int totalDismissingDistance = 0; - if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) { - mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP; - totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position - - mDividerSnapAlgorithm.getFirstSplitTarget().position; - } else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) { - mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM; - totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position - - mDividerSnapAlgorithm.getDismissEndTarget().position; - } - - final boolean topLeftShrink = isLeftRightSplit - ? position < getTopLeftContentBounds().right - : position < getTopLeftContentBounds().bottom; - if (topLeftShrink) { - mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP; - mContentBounds.set(getTopLeftContentBounds()); - mSurfaceBounds.set(getTopLeftBounds()); - } else { - mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM; - mContentBounds.set(getBottomRightContentBounds()); - mSurfaceBounds.set(getBottomRightBounds()); - } - - if (mDismissingSide != DOCKED_INVALID) { - float fraction = Math.max(0, - Math.min(mDividerSnapAlgorithm.calculateDismissingFraction(position), 1f)); - mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction); - if (mParallaxType == PARALLAX_DISMISSING) { - fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide); - if (isLeftRightSplit) { - mParallaxOffset.x = (int) (fraction * totalDismissingDistance); - } else { - mParallaxOffset.y = (int) (fraction * totalDismissingDistance); - } - } - } - - if (mParallaxType == PARALLAX_ALIGN_CENTER) { - if (isLeftRightSplit) { - mParallaxOffset.x = - (mSurfaceBounds.width() - mContentBounds.width()) / 2; - } else { - mParallaxOffset.y = - (mSurfaceBounds.height() - mContentBounds.height()) / 2; - } - } - } - - /** - * @return for a specified {@code fraction}, this returns an adjusted value that simulates a - * slowing down parallax effect - */ - private float calculateParallaxDismissingFraction(float fraction, int dockSide) { - float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; - - // Less parallax at the top, just because. - if (dockSide == WindowManager.DOCKED_TOP) { - result /= 2f; - } - return result; - } - - /** Applies parallax offset and dimming value to the root surface at the dismissing side. */ - void adjustRootSurface(SurfaceControl.Transaction t, - SurfaceControl leash1, SurfaceControl leash2) { - SurfaceControl targetLeash = null; - - if (mParallaxType == PARALLAX_DISMISSING) { - switch (mDismissingSide) { - case DOCKED_TOP: - case DOCKED_LEFT: - targetLeash = leash1; - mTempRect.set(getTopLeftBounds()); - break; - case DOCKED_BOTTOM: - case DOCKED_RIGHT: - targetLeash = leash2; - mTempRect.set(getBottomRightBounds()); - break; - } - } else if (mParallaxType == PARALLAX_ALIGN_CENTER) { - switch (mShrinkSide) { - case DOCKED_TOP: - case DOCKED_LEFT: - targetLeash = leash1; - mTempRect.set(getTopLeftBounds()); - break; - case DOCKED_BOTTOM: - case DOCKED_RIGHT: - targetLeash = leash2; - mTempRect.set(getBottomRightBounds()); - break; - } - } - if (mParallaxType != PARALLAX_NONE && targetLeash != null) { - t.setPosition(targetLeash, - mTempRect.left + mParallaxOffset.x, mTempRect.top + mParallaxOffset.y); - // Transform the screen-based split bounds to surface-based crop bounds. - mTempRect.offsetTo(-mParallaxOffset.x, -mParallaxOffset.y); - t.setWindowCrop(targetLeash, mTempRect); - } - } - - void adjustDimSurface(SurfaceControl.Transaction t, - SurfaceControl dimLayer1, SurfaceControl dimLayer2) { - SurfaceControl targetDimLayer; - switch (mDismissingSide) { - case DOCKED_TOP: - case DOCKED_LEFT: - targetDimLayer = dimLayer1; - break; - case DOCKED_BOTTOM: - case DOCKED_RIGHT: - targetDimLayer = dimLayer2; - break; - case DOCKED_INVALID: - default: - t.setAlpha(dimLayer1, 0).hide(dimLayer1); - t.setAlpha(dimLayer2, 0).hide(dimLayer2); - return; - } - t.setAlpha(targetDimLayer, mDismissingDimValue) - .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f); - } - } - /** Records IME top offset changes and updates SplitLayout correspondingly. */ private class ImePositionProcessor implements DisplayImeController.ImePositionProcessor { /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java index 9c951bd89876..6f1dc56e273a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java @@ -43,7 +43,7 @@ import java.util.Map; */ public class SplitSpec { private static final String TAG = "SplitSpec"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; /** A split ratio used on larger screens, where we can fit both apps onscreen. */ public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f; 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 535112f5124a..8ecf483a7e75 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 @@ -35,7 +35,9 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.Flags.enableFlexibleSplit; +import static com.android.wm.shell.Flags.enableFlexibleTwoAppSplit; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; +import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; @@ -127,7 +129,6 @@ import com.android.internal.logging.InstanceId; import com.android.internal.policy.FoldLockSettingsObserver; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; -import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -2007,7 +2008,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If all stages are filled, create new SplitBounds and update Recents. if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition(); - if (Flags.enableFlexibleTwoAppSplit()) { + if (enableFlexibleTwoAppSplit()) { // Split screen can be laid out in such a way that some of the apps are // offscreen. For the purposes of passing SplitBounds up to launcher (for use in // thumbnails etc.), we crop the bounds down to the screen size. @@ -2064,10 +2065,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTaskLeash = leash; if (mSplitLayout == null) { + int parallaxType = enableFlexibleTwoAppSplit() ? PARALLAX_FLEX : PARALLAX_ALIGN_CENTER; mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, mRootTaskInfo.configuration, this, mParentContainerCallbacks, - mDisplayController, mDisplayImeController, mTaskOrganizer, - PARALLAX_ALIGN_CENTER /* parallaxType */, mSplitState, mMainHandler); + mDisplayController, mDisplayImeController, mTaskOrganizer, parallaxType, + mSplitState, mMainHandler); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } @@ -2407,6 +2409,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, updateSurfaceBounds(layout, t, shouldUseParallaxEffect); getMainStageBounds(mTempRect1); getSideStageBounds(mTempRect2); + Rect displayBounds = mSplitLayout.getRootBounds(); + if (enableFlexibleSplit()) { StageTaskListener ltStage = mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT, @@ -2414,12 +2418,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, StageTaskListener brStage = mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /*checkAllStagesIfNotActive*/); - ltStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately); - brStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately); + ltStage.onResizing(mTempRect1, mTempRect2, displayBounds, t, offsetX, offsetY, + mShowDecorImmediately); + brStage.onResizing(mTempRect2, mTempRect1, displayBounds, t, offsetX, offsetY, + mShowDecorImmediately); } else { - mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, + mMainStage.onResizing(mTempRect1, mTempRect2, displayBounds, t, offsetX, offsetY, mShowDecorImmediately); - mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, + mSideStage.onResizing(mTempRect2, mTempRect1, displayBounds, t, offsetX, offsetY, mShowDecorImmediately); } t.apply(); @@ -2466,7 +2472,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.populateTouchZones(); }, mainDecor, sideDecor, decorManagers); - if (Flags.enableFlexibleTwoAppSplit()) { + if (enableFlexibleTwoAppSplit()) { switch (layout.calculateCurrentSnapPosition()) { case SNAP_TO_2_10_90 -> grantFocusToPosition(false /* leftOrTop */); case SNAP_TO_2_90_10 -> grantFocusToPosition(true /* leftOrTop */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 816f51f997d5..29751986959b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -339,11 +339,11 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId; } - void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, - int offsetY, boolean immediately) { + void onResizing(Rect newBounds, Rect sideBounds, Rect displayBounds, + SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately) { if (mSplitDecorManager != null && mRootTaskInfo != null) { - mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX, - offsetY, immediately); + mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, displayBounds, t, + offsetX, offsetY, immediately); } } diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index cc4a29b31996..12b1dd794a03 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -80,6 +80,7 @@ cc_library { "LoadedArsc.cpp", "Locale.cpp", "LocaleData.cpp", + "LocaleDataLookup.cpp", "misc.cpp", "NinePatch.cpp", "ObbFile.cpp", @@ -225,6 +226,7 @@ cc_test { "tests/Idmap_test.cpp", "tests/LoadedArsc_test.cpp", "tests/Locale_test.cpp", + "tests/LocaleDataLookup_test.cpp", "tests/NinePatch_test.cpp", "tests/ResourceTimer_test.cpp", "tests/ResourceUtils_test.cpp", diff --git a/libs/androidfw/LocaleData.cpp b/libs/androidfw/LocaleData.cpp index 020cef6012e9..1b23d90c5ab3 100644 --- a/libs/androidfw/LocaleData.cpp +++ b/libs/androidfw/LocaleData.cpp @@ -23,39 +23,18 @@ #include <unordered_set> #include <androidfw/LocaleData.h> +#include <androidfw/LocaleDataLookup.h> namespace android { -#include "LocaleDataTables.cpp" - -inline uint32_t packLocale(const char* language, const char* region) { - return (((uint8_t) language[0]) << 24u) | (((uint8_t) language[1]) << 16u) | - (((uint8_t) region[0]) << 8u) | ((uint8_t) region[1]); -} - -inline uint32_t dropRegion(uint32_t packed_locale) { - return packed_locale & 0xFFFF0000LU; -} - -inline bool hasRegion(uint32_t packed_locale) { - return (packed_locale & 0x0000FFFFLU) != 0; -} - -const size_t SCRIPT_LENGTH = 4; -const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]); const uint32_t PACKED_ROOT = 0; // to represent the root locale +const uint32_t MAX_PARENT_DEPTH = getMaxAncestorTreeDepth(); uint32_t findParent(uint32_t packed_locale, const char* script) { if (hasRegion(packed_locale)) { - for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) { - if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) { - auto map = SCRIPT_PARENTS[i].map; - auto lookup_result = map->find(packed_locale); - if (lookup_result != map->end()) { - return lookup_result->second; - } - break; - } + auto parent_key = findParentLocalePackedKey(script, packed_locale); + if (parent_key != 0) { + return parent_key; } return dropRegion(packed_locale); } @@ -111,17 +90,6 @@ size_t findDistance(uint32_t supported, return supported_ancestor_count + request_ancestors_index - 1; } -inline bool isRepresentative(uint32_t language_and_region, const char* script) { - const uint64_t packed_locale = ( - (((uint64_t) language_and_region) << 32u) | - (((uint64_t) script[0]) << 24u) | - (((uint64_t) script[1]) << 16u) | - (((uint64_t) script[2]) << 8u) | - ((uint64_t) script[3])); - - return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0); -} - const uint32_t US_SPANISH = 0x65735553LU; // es-US const uint32_t MEXICAN_SPANISH = 0x65734D58LU; // es-MX const uint32_t LATIN_AMERICAN_SPANISH = 0x6573A424LU; // es-419 @@ -185,8 +153,8 @@ int localeDataCompareRegions( // If we are here, left and right are equidistant from the request. We will // try and see if any of them is a representative locale. - const bool left_is_representative = isRepresentative(left, requested_script); - const bool right_is_representative = isRepresentative(right, requested_script); + const bool left_is_representative = isLocaleRepresentative(left, requested_script); + const bool right_is_representative = isLocaleRepresentative(right, requested_script); if (left_is_representative != right_is_representative) { return (int) left_is_representative - (int) right_is_representative; } @@ -204,14 +172,14 @@ void localeDataComputeScript(char out[4], const char* language, const char* regi return; } uint32_t lookup_key = packLocale(language, region); - auto lookup_result = LIKELY_SCRIPTS.find(lookup_key); - if (lookup_result == LIKELY_SCRIPTS.end()) { + auto lookup_result = lookupLikelyScript(lookup_key); + if (lookup_result == nullptr) { // We couldn't find the locale. Let's try without the region if (region[0] != '\0') { lookup_key = dropRegion(lookup_key); - lookup_result = LIKELY_SCRIPTS.find(lookup_key); - if (lookup_result != LIKELY_SCRIPTS.end()) { - memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH); + lookup_result = lookupLikelyScript(lookup_key); + if (lookup_result != nullptr) { + memcpy(out, lookup_result, SCRIPT_LENGTH); return; } } @@ -220,7 +188,7 @@ void localeDataComputeScript(char out[4], const char* language, const char* regi return; } else { // We found the locale. - memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH); + memcpy(out, lookup_result, SCRIPT_LENGTH); } } diff --git a/libs/androidfw/LocaleDataLookup.cpp b/libs/androidfw/LocaleDataLookup.cpp new file mode 100644 index 000000000000..5441e2258900 --- /dev/null +++ b/libs/androidfw/LocaleDataLookup.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <unordered_map> +#include <unordered_set> + +#include <androidfw/LocaleDataLookup.h> + +namespace android { + +#include "LocaleDataTables.cpp" + +const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]); + +const char* lookupLikelyScript(uint32_t packed_lang_region) { + + auto lookup_result = LIKELY_SCRIPTS.find(packed_lang_region); + if (lookup_result == LIKELY_SCRIPTS.end()) { + return nullptr; + } else { + return SCRIPT_CODES[lookup_result->second]; + } +} + +uint32_t findParentLocalePackedKey(const char* script, uint32_t packed_lang_region) { + for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) { + if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) { + auto map = SCRIPT_PARENTS[i].map; + auto lookup_result = map->find(packed_lang_region); + if (lookup_result != map->end()) { + return lookup_result->second; + } + break; + } + } + return 0; +} + +uint32_t getMaxAncestorTreeDepth() { + return MAX_PARENT_DEPTH; +} + +namespace hidden { + +bool isRepresentative(uint64_t packed_locale) { + return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0); +} + +} // namespace hidden + +} // namespace android diff --git a/libs/androidfw/include/androidfw/LocaleDataLookup.h b/libs/androidfw/include/androidfw/LocaleDataLookup.h new file mode 100644 index 000000000000..7fde7123ed0b --- /dev/null +++ b/libs/androidfw/include/androidfw/LocaleDataLookup.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 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. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> + + +namespace android { + +namespace hidden { + bool isRepresentative(uint64_t packed_locale); +} + +constexpr size_t SCRIPT_LENGTH = 4; + +constexpr inline uint32_t packLocale(const char* language, const char* region) { + const unsigned char* lang = reinterpret_cast<const unsigned char*>(language); + const unsigned char* reg = reinterpret_cast<const unsigned char*>(region); + return (static_cast<uint32_t>(lang[0]) << 24u) | + (static_cast<uint32_t>(lang[1]) << 16u) | + (static_cast<uint32_t>(reg[0]) << 8u) | + static_cast<uint32_t>(reg[1]); +} + +constexpr inline uint32_t dropRegion(uint32_t packed_locale) { + return packed_locale & 0xFFFF0000LU; +} + +constexpr inline bool hasRegion(uint32_t packed_locale) { + return (packed_locale & 0x0000FFFFLU) != 0; +} + +/** + * Return nullptr if the key isn't found. The input packed_lang_region can be computed + * by android::packLocale. + * Note that the returned char* is either nullptr or 4-byte char seqeuence, but isn't + * a null-terminated string. + */ +const char* lookupLikelyScript(uint32_t packed_lang_region); +/** + * Return false if the key isn't representative. The input lookup key can be computed + * by android::packLocale. + */ +bool inline isLocaleRepresentative(uint32_t language_and_region, const char* script) { + const unsigned char* s = reinterpret_cast<const unsigned char*>(script); + const uint64_t packed_locale = ( + ((static_cast<uint64_t>(language_and_region)) << 32u) | + (static_cast<uint64_t>(s[0]) << 24u) | + (static_cast<uint64_t>(s[1]) << 16u) | + (static_cast<uint64_t>(s[2]) << 8u) | + static_cast<uint64_t>(s[3])); + + return hidden::isRepresentative(packed_locale); +} + +/** + * Return a parent packed key for a given script and child packed key. Return 0 if + * no parent is found. + */ +uint32_t findParentLocalePackedKey(const char* script, uint32_t packed_lang_region); + +uint32_t getMaxAncestorTreeDepth(); + +} // namespace android diff --git a/libs/androidfw/tests/LocaleDataLookup_test.cpp b/libs/androidfw/tests/LocaleDataLookup_test.cpp new file mode 100644 index 000000000000..26b220d63169 --- /dev/null +++ b/libs/androidfw/tests/LocaleDataLookup_test.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "androidfw/LocaleDataLookup.h" + +#include <cstddef> +#include <string> + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + + +namespace android { + +constexpr const char NULL_SCRIPT[4] = {'\0', '\0', '\0','\0' }; + +#define EXPECT_SCEIPT_EQ(ex, s) EXPECT_EQ(0, s == nullptr ? -1 : memcmp(ex, s, 4)) + +// Similar to packLanguageOrRegion() in ResourceTypes.cpp +static uint32_t encodeLanguageOrRegionLiteral(const char* in, const char base) { + size_t len = strlen(in); + if (len <= 1) { + return 0; + } + + if (len == 2) { + return (((uint8_t) in[0]) << 8) | ((uint8_t) in[1]); + } + uint8_t first = (in[0] - base) & 0x007f; + uint8_t second = (in[1] - base) & 0x007f; + uint8_t third = (in[2] - base) & 0x007f; + + return ((uint8_t) (0x80 | (third << 2) | (second >> 3)) << 8) | ((second << 5) | first); +} + +static uint32_t encodeLocale(const char* language, const char* region) { + return (encodeLanguageOrRegionLiteral(language, 'a') << 16) | + encodeLanguageOrRegionLiteral(region, '0'); +} + +TEST(LocaleDataLookupTest, lookupLikelyScript) { + EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("", ""))); + EXPECT_SCEIPT_EQ("Latn", lookupLikelyScript(encodeLocale("en", ""))); + EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("en", "US"))); + EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("en", "GB"))); + EXPECT_SCEIPT_EQ("Latn", lookupLikelyScript(encodeLocale("fr", ""))); + EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("fr", "FR"))); + + + EXPECT_SCEIPT_EQ("~~~A", lookupLikelyScript(encodeLocale("en", "XA"))); + EXPECT_SCEIPT_EQ("Latn", lookupLikelyScript(encodeLocale("ha", ""))); + EXPECT_SCEIPT_EQ("Arab", lookupLikelyScript(encodeLocale("ha", "SD"))); + EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("ha", "Sd"))); // case sensitive + EXPECT_SCEIPT_EQ("Hans", lookupLikelyScript(encodeLocale("zh", ""))); + EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("zh", "CN"))); + EXPECT_SCEIPT_EQ("Hant", lookupLikelyScript(encodeLocale("zh", "HK"))); + + EXPECT_SCEIPT_EQ("Nshu", lookupLikelyScript(encodeLocale("zhx", ""))); + EXPECT_SCEIPT_EQ("Nshu", lookupLikelyScript(0xDCF90000u)); // encoded "zhx" +} + +TEST(LocaleDataLookupTest, isLocaleRepresentative) { + EXPECT_TRUE(isLocaleRepresentative(encodeLocale("en", "US"), "Latn")); + EXPECT_TRUE(isLocaleRepresentative(encodeLocale("en", "GB"), "Latn")); + EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", "US"), NULL_SCRIPT)); + EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", ""), "Latn")); + EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", ""), NULL_SCRIPT)); + EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", "US"), "Arab")); + + EXPECT_TRUE(isLocaleRepresentative(encodeLocale("fr", "FR"), "Latn")); + + EXPECT_TRUE(isLocaleRepresentative(encodeLocale("zh", "CN"), "Hans")); + EXPECT_FALSE(isLocaleRepresentative(encodeLocale("zh", "TW"), "Hans")); + EXPECT_FALSE(isLocaleRepresentative(encodeLocale("zhx", "CN"), "Hans")); + EXPECT_FALSE(isLocaleRepresentative(0xDCF9434E, "Hans")); + EXPECT_TRUE(isLocaleRepresentative(encodeLocale("zhx", "CN"), "Nshu")); + EXPECT_TRUE(isLocaleRepresentative(0xDCF9434E, "Nshu")); +} + +TEST(LocaleDataLookupTest, findParentLocalePackedKey) { + EXPECT_EQ(encodeLocale("en", "001"), findParentLocalePackedKey("Latn", encodeLocale("en", "GB"))); + EXPECT_EQ(0x656E8400u, findParentLocalePackedKey("Latn", encodeLocale("en", "GB"))); + + EXPECT_EQ(encodeLocale("en", "IN"), findParentLocalePackedKey("Deva", encodeLocale("hi", ""))); + + EXPECT_EQ(encodeLocale("ar", "015"), findParentLocalePackedKey("Arab", encodeLocale("ar", "AE"))); + EXPECT_EQ(0x61729420u, findParentLocalePackedKey("Arab", encodeLocale("ar", "AE"))); + + EXPECT_EQ(encodeLocale("ar", "015"), findParentLocalePackedKey("~~~B", encodeLocale("ar", "XB"))); + EXPECT_EQ(0x61729420u, findParentLocalePackedKey("Arab", encodeLocale("ar", "AE"))); + + EXPECT_EQ(encodeLocale("zh", "HK"), findParentLocalePackedKey("Hant", encodeLocale("zh", "MO"))); +} + +} // namespace android diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index f1f03c31f718..ed193515b382 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2470,7 +2470,7 @@ public class SettingsProvider extends ContentProvider { boolean isRestrictedShell = android.security.Flags.protectDeviceConfigFlags() && hasAllowlistPermission; - if (!isRestrictedShell && hasWritePermission) { + if (hasWritePermission) { assertCallingUserDenyList(flags); } else if (hasAllowlistPermission) { Set<String> allowlistedDeviceConfigNamespaces = null; @@ -2500,7 +2500,7 @@ public class SettingsProvider extends ContentProvider { } if (!namespaceAllowed && !DeviceConfig.getAdbWritableFlags().contains(flag)) { - Slog.wtf(LOG_TAG, "Permission denial for flag '" + flag + throw new SecurityException("Permission denial for flag '" + flag + "'; allowlist permission granted, but must add flag to the " + "allowlist"); } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index a945c33bc20a..9355bf8e4142 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -634,7 +634,7 @@ public class SettingsProviderTest extends BaseSettingsProviderTest { throws Exception { setSettingAndAssertSuccessfulChange(() -> { insertStringViaProviderApi(type, name, value, withTableRowUri); - }, type, name, value, UserHandle.USER_SYSTEM); + }, type, name, value, getContext().getUserId()); } private void setSettingAndAssertSuccessfulChange(Runnable setCommand, final int type, diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index c6555041164d..61f49db07abc 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -2419,8 +2419,8 @@ public class BugreportProgressService extends Service { bugreportLocationInfo = new BugreportLocationInfo(readFile(in)); int screenshotSize = in.readInt(); + screenshotLocationInfo = new ScreenshotLocationInfo(null); for (int i = 1; i <= screenshotSize; i++) { - screenshotLocationInfo = new ScreenshotLocationInfo(null); screenshotLocationInfo.mScreenshotFiles.add(readFile(in)); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java index e00f6a6d6418..e94f04f8b35c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java @@ -152,8 +152,8 @@ public class AccessibilityTargetAdapterTest extends SysuiTestCase { @EnableFlags( com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) public void setBadgeOnLeftSide_false_rightBadgeVisibleAndLeftBadgeInvisible() { - when(mAccessibilityTarget.getId()).thenReturn( - ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + when(mAccessibilityTarget.getId()) + .thenReturn(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); mAdapter.setBadgeOnLeftSide(false); mAdapter.onBindViewHolder(mViewHolder, 0); @@ -166,8 +166,8 @@ public class AccessibilityTargetAdapterTest extends SysuiTestCase { @EnableFlags( com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) public void setBadgeOnLeftSide_rightBadgeInvisibleAndLeftBadgeVisible() { - when(mAccessibilityTarget.getId()).thenReturn( - ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + when(mAccessibilityTarget.getId()) + .thenReturn(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); mAdapter.setBadgeOnLeftSide(true); mAdapter.onBindViewHolder(mViewHolder, 0); @@ -180,8 +180,8 @@ public class AccessibilityTargetAdapterTest extends SysuiTestCase { @EnableFlags( com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) public void setBadgeOnLeftSide_bindViewHolderPayloads_rightBadgeInvisibleAndLeftBadgeVisible() { - when(mAccessibilityTarget.getId()).thenReturn( - ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + when(mAccessibilityTarget.getId()) + .thenReturn(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); mAdapter.setBadgeOnLeftSide(true); mAdapter.onBindViewHolder(mViewHolder, 0, List.of(PAYLOAD_HEARING_STATUS_DRAWABLE)); diff --git a/packages/SystemUI/res/drawable/volume_dialog_background.xml b/packages/SystemUI/res/drawable/volume_dialog_background.xml index 25d78e3474d5..b527506e28f5 100644 --- a/packages/SystemUI/res/drawable/volume_dialog_background.xml +++ b/packages/SystemUI/res/drawable/volume_dialog_background.xml @@ -17,6 +17,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <corners android:radius="@dimen/volume_dialog_background_corner_radius" /> + <corners android:bottomLeftRadius="@dimen/volume_dialog_background_corner_radius" + android:bottomRightRadius="@dimen/volume_dialog_background_corner_radius"/> <solid android:color="@androidprv:color/materialColorSurface" /> </shape> diff --git a/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml b/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml index 2694435bcc78..d7607cc17f2a 100644 --- a/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml +++ b/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml @@ -17,5 +17,5 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> <corners android:radius="20dp" /> - <solid android:color="?androidprv:attr/colorSurface" /> + <solid android:color="@androidprv:color/materialColorSurface" /> </shape> diff --git a/packages/SystemUI/res/drawable/volume_dialog_ringer_background.xml b/packages/SystemUI/res/drawable/volume_dialog_ringer_background.xml new file mode 100644 index 000000000000..6a706f739267 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_dialog_ringer_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2024 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/volume_dialog_background_corner_radius"/> + <solid android:color="@androidprv:color/materialColorSurface" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 327075967dba..0ec668012cfc 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -19,6 +19,7 @@ android:id="@+id/volume_dialog_root" android:layout_width="match_parent" android:layout_height="match_parent" + android:clipChildren="false" app:layoutDescription="@xml/volume_dialog_scene"> <View @@ -31,29 +32,19 @@ app:layout_constraintBottom_toBottomOf="@id/volume_dialog_settings" app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container" - app:layout_constraintTop_toTopOf="@id/volume_ringer_drawer" /> - - <View - android:id="@+id/volume_ringer_horizontal_background" - android:layout_width="0dp" - android:layout_height="0dp" - android:background="@drawable/volume_dialog_background" - app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container" - app:layout_constraintEnd_toEndOf="@id/volume_ringer_drawer" - app:layout_constraintStart_toStartOf="@id/volume_ringer_drawer" - app:layout_constraintTop_toTopOf="@id/volume_dialog_background" /> + app:layout_constraintTop_toTopOf="@id/volume_dialog_main_slider_container" /> <include android:id="@id/volume_ringer_drawer" layout="@layout/volume_ringer_drawer" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/volume_dialog_components_spacing" - android:layout_marginRight="@dimen/volume_dialog_ringer_drawer_diff_right_margin" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginEnd="@dimen/volume_dialog_ringer_drawer_diff_end_margin" + android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin" app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container" app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="1" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> <include android:id="@+id/volume_dialog_main_slider_container" diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index cd8f18f7dcdb..983603a95833 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -18,13 +18,21 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/volume_ringer_drawer" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:gravity="center" android:layoutDirection="ltr" app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene"> + <View + android:id="@+id/ringer_buttons_background" + android:layout_width="@dimen/volume_dialog_width" + android:layout_height="0dp" + android:layout_marginTop="@dimen/volume_dialog_background_vertical_margin" + android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin" + android:background="@drawable/volume_dialog_ringer_background" /> + <!-- add ringer buttons here --> </androidx.constraintlayout.motion.widget.MotionLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 570197edf876..33e29c11254c 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -2120,8 +2120,8 @@ <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen> - <dimen name="volume_dialog_ringer_drawer_left_margin">10dp</dimen> - <dimen name="volume_dialog_ringer_drawer_diff_right_margin">6dp</dimen> + <dimen name="volume_dialog_ringer_drawer_margin">10dp</dimen> + <dimen name="volume_dialog_ringer_drawer_diff_end_margin">6dp</dimen> <dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen> <dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen> <dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java index 42460617d476..a8de43332556 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java @@ -140,7 +140,7 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie @Override protected void onViewDetached() { // TODO(b/117344873) Remove below work around after this issue be fixed. - if (mDisplayId == mDisplayTracker.getDefaultDisplayId()) { + if (mDisplayId == mDisplayTracker.getDefaultDisplayId() && mLiveData != null) { mLiveData.removeObserver(mObserver); } mConfigurationController.removeCallback(mConfigurationListener); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java index 697d16b3336c..277c81ee9e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java @@ -48,8 +48,7 @@ import java.util.List; * An adapter which shows the set of accessibility targets that can be performed. */ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { - @VisibleForTesting - static final int PAYLOAD_HEARING_STATUS_DRAWABLE = 1; + @VisibleForTesting static final int PAYLOAD_HEARING_STATUS_DRAWABLE = 1; private int mIconWidthHeight; private int mBadgeWidthHeight; diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt new file mode 100644 index 000000000000..cb6d6f32923d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 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.keyboard.shortcut + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.UserHandle +import com.android.systemui.CoreStartable +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository +import com.android.systemui.statusbar.CommandQueue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + + +@SysUISingleton +class ShortcutHelperCoreStartable +@Inject constructor( + private val commandQueue: CommandQueue, + private val broadcastDispatcher: BroadcastDispatcher, + private val stateRepository: ShortcutHelperStateRepository, + @Background private val backgroundScope: CoroutineScope, +) : CoreStartable { + override fun start() { + registerBroadcastReceiver( + action = Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS, + onReceive = { + backgroundScope.launch { stateRepository.show() } + } + ) + registerBroadcastReceiver( + action = Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS, + onReceive = { stateRepository.hide() } + ) + registerBroadcastReceiver( + action = Intent.ACTION_CLOSE_SYSTEM_DIALOGS, + onReceive = { stateRepository.hide() } + ) + commandQueue.addCallback( + object : CommandQueue.Callbacks { + override fun dismissKeyboardShortcutsMenu() { + stateRepository.hide() + } + + override fun toggleKeyboardShortcutsMenu(deviceId: Int) { + backgroundScope.launch { stateRepository.toggle(deviceId) } + } + } + ) + } + + private fun registerBroadcastReceiver(action: String, onReceive: () -> Unit) { + broadcastDispatcher.registerReceiver( + receiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + onReceive() + } + }, + filter = IntentFilter(action), + flags = Context.RECEIVER_EXPORTED or Context.RECEIVER_VISIBLE_TO_INSTANT_APPS, + user = UserHandle.ALL, + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt index 1af7340ad7b4..d8532c176619 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt @@ -21,7 +21,6 @@ import com.android.systemui.Flags.keyboardShortcutHelperRewrite import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository -import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource @@ -95,8 +94,8 @@ interface ShortcutHelperModule { @Provides @IntoMap - @ClassKey(ShortcutHelperStateRepository::class) - fun repo(implLazy: Lazy<ShortcutHelperStateRepository>): CoreStartable { + @ClassKey(ShortcutHelperCoreStartable::class) + fun repo(implLazy: Lazy<ShortcutHelperCoreStartable>): CoreStartable { return if (keyboardShortcutHelperRewrite()) { implLazy.get() } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepository.kt index aa6b61b6215d..42a13f58cdf7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepository.kt @@ -16,73 +16,44 @@ package com.android.systemui.keyboard.shortcut.data.repository -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter import android.hardware.input.InputManager -import android.os.UserHandle import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD -import com.android.systemui.CoreStartable -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Inactive import com.android.systemui.shared.hardware.findInputDevice -import com.android.systemui.statusbar.CommandQueue -import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import com.android.app.tracing.coroutines.launchTraced as launch +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext +import javax.inject.Inject @SysUISingleton class ShortcutHelperStateRepository @Inject constructor( - private val commandQueue: CommandQueue, - private val broadcastDispatcher: BroadcastDispatcher, private val inputManager: InputManager, - @Background private val backgroundScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, -) : CoreStartable { +) { + private val _state = MutableStateFlow<ShortcutHelperState>(Inactive) + val state = _state.asStateFlow() - val state = MutableStateFlow<ShortcutHelperState>(Inactive) + suspend fun toggle(deviceId: Int? = null) { + if (_state.value is Inactive) { + show(deviceId) + } else { + hide() + } + } - override fun start() { - registerBroadcastReceiver( - action = Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS, - onReceive = { - backgroundScope.launch { state.value = Active(findPhysicalKeyboardId()) } - } - ) - registerBroadcastReceiver( - action = Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS, - onReceive = { state.value = Inactive } - ) - registerBroadcastReceiver( - action = Intent.ACTION_CLOSE_SYSTEM_DIALOGS, - onReceive = { state.value = Inactive } - ) - commandQueue.addCallback( - object : CommandQueue.Callbacks { - override fun dismissKeyboardShortcutsMenu() { - state.value = Inactive - } + suspend fun show(deviceId: Int? = null) { + _state.value = Active(deviceId ?: findPhysicalKeyboardId()) + } - override fun toggleKeyboardShortcutsMenu(deviceId: Int) { - state.value = - if (state.value is Inactive) { - Active(deviceId) - } else { - Inactive - } - } - } - ) + fun hide() { + _state.value = Inactive } private suspend fun findPhysicalKeyboardId() = @@ -92,21 +63,4 @@ constructor( return@withContext firstEnabledPhysicalKeyboard?.id ?: VIRTUAL_KEYBOARD } - fun hide() { - state.value = Inactive - } - - private fun registerBroadcastReceiver(action: String, onReceive: () -> Unit) { - broadcastDispatcher.registerReceiver( - receiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - onReceive() - } - }, - filter = IntentFilter(action), - flags = Context.RECEIVER_EXPORTED or Context.RECEIVER_VISIBLE_TO_INSTANT_APPS, - user = UserHandle.ALL, - ) - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt index cea3b6442feb..5505189e3a33 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt @@ -23,10 +23,9 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState import com.android.systemui.model.SysUiState import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.QuickStepContract -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton @@ -39,7 +38,7 @@ constructor( private val repository: ShortcutHelperStateRepository ) { - val state: Flow<ShortcutHelperState> = repository.state.asStateFlow() + val state: Flow<ShortcutHelperState> = repository.state fun onViewClosed() { repository.hide() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java index 82367ebb8c76..ee53471253af 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java @@ -514,7 +514,7 @@ public class InternetDialogDelegateLegacy implements } private void setMobileDataLayout(InternetContent internetContent) { - if (!internetContent.mShouldUpdateMobileNetwork && mDialog == null) { + if (!internetContent.mShouldUpdateMobileNetwork || mDialog == null) { return; } setMobileDataLayout(mDialog, internetContent); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt index 8733eeb2bd05..58b8f6254d9d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt @@ -71,8 +71,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { fun CoroutineScope.bind(view: View) { val volumeDialogBackgroundView = view.requireViewById<View>(R.id.volume_dialog_background) - val ringerHBackgroundView = - view.requireViewById<View>(R.id.volume_ringer_horizontal_background) + val ringerBackgroundView = view.requireViewById<View>(R.id.ringer_buttons_background) val drawerContainer = view.requireViewById<MotionLayout>(R.id.volume_ringer_drawer) val unselectedButtonUiModel = RingerButtonUiModel.getUnselectedButton(view.context) val selectedButtonUiModel = RingerButtonUiModel.getSelectedButton(view.context) @@ -86,15 +85,17 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { ) var backgroundAnimationProgress: Float by Delegates.observable(0F) { _, _, progress -> - ringerHBackgroundView.applyCorners( + ringerBackgroundView.applyCorners( fullRadius = volumeDialogBgFullRadius, diff = volumeDialogBgFullRadius - volumeDialogBgSmallRadius, progress, + isBottom = false, ) volumeDialogBackgroundView.applyCorners( fullRadius = volumeDialogBgFullRadius, diff = volumeDialogBgFullRadius - volumeDialogBgSmallRadius, progress, + isBottom = true, ) } val ringerDrawerTransitionListener = VolumeDialogRingerDrawerTransitionListener { @@ -102,7 +103,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { } drawerContainer.setTransitionListener(ringerDrawerTransitionListener) volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate() - ringerHBackgroundView.background = ringerHBackgroundView.background.mutate() + ringerBackgroundView.background = ringerBackgroundView.background.mutate() viewModel.ringerViewModel .mapLatest { ringerState -> @@ -192,8 +193,8 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { ) volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate() - ringerHBackgroundView.background = - ringerHBackgroundView.background.mutate() + ringerBackgroundView.background = + ringerBackgroundView.background.mutate() } } } @@ -203,8 +204,8 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { volumeDialogBackgroundView.setBackgroundResource( R.drawable.volume_dialog_background ) - ringerHBackgroundView.setBackgroundResource( - R.drawable.volume_dialog_background + ringerBackgroundView.setBackgroundResource( + R.drawable.volume_dialog_ringer_background ) } } @@ -227,14 +228,14 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { ) { val count = uiModel.availableButtons.size val selectedButton = - getChildAt(count - uiModel.currentButtonIndex - 1) + getChildAt(count - uiModel.currentButtonIndex) .requireViewById<ImageButton>(R.id.volume_drawer_button) val previousIndex = uiModel.availableButtons.indexOfFirst { it?.ringerMode == uiModel.drawerState.previousMode } val unselectedButton = - getChildAt(count - previousIndex - 1) + getChildAt(count - previousIndex) .requireViewById<ImageButton>(R.id.volume_drawer_button) // We only need to execute on roundness animation end and volume dialog background // progress update once because these changes should be applied once on volume dialog @@ -282,7 +283,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { val count = uiModel.availableButtons.size uiModel.availableButtons.fastForEachIndexed { index, ringerButton -> ringerButton?.let { - val view = getChildAt(count - index - 1) + val view = getChildAt(count - index) val isOpen = uiModel.drawerState is RingerDrawerState.Open if (index == uiModel.currentButtonIndex) { view.bindDrawerButton( @@ -335,7 +336,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { } private fun MotionLayout.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) { - val childCountDelta = childCount - count + val childCountDelta = childCount - count - 1 when { childCountDelta > 0 -> { removeViews(0, childCountDelta) @@ -395,8 +396,14 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { } } - private fun View.applyCorners(fullRadius: Int, diff: Int, progress: Float) { - (background as GradientDrawable).cornerRadius = fullRadius - progress * diff + private fun View.applyCorners(fullRadius: Int, diff: Int, progress: Float, isBottom: Boolean) { + val radius = fullRadius - progress * diff + (background as GradientDrawable).cornerRadii = + if (isBottom) { + floatArrayOf(0F, 0F, 0F, 0F, radius, radius, radius, radius) + } else { + FloatArray(8) { radius } + } background.invalidateSelf() } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt index 69ffa3890437..c1e003727750 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.ringer.ui.util import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.util.TypedValue import android.view.View import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.widget.ConstraintSet @@ -41,17 +42,6 @@ private fun ConstraintSet.setButtonPositionPortraitConstraints( index: Int, button: View, ) { - if (motionLayout.getChildAt(index - 1) == null) { - connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP) - } else { - connect( - button.id, - ConstraintSet.TOP, - motionLayout.getChildAt(index - 1).id, - ConstraintSet.BOTTOM, - ) - } - if (motionLayout.getChildAt(index + 1) == null) { connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM) } else { @@ -62,10 +52,7 @@ private fun ConstraintSet.setButtonPositionPortraitConstraints( ConstraintSet.TOP, ) } - connect(button.id, ConstraintSet.START, motionLayout.id, ConstraintSet.START) connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END) - clear(button.id, ConstraintSet.LEFT) - clear(button.id, ConstraintSet.RIGHT) } private fun ConstraintSet.setButtonPositionLandscapeConstraints( @@ -73,93 +60,120 @@ private fun ConstraintSet.setButtonPositionLandscapeConstraints( index: Int, button: View, ) { - if (motionLayout.getChildAt(index - 1) == null) { - connect(button.id, ConstraintSet.LEFT, motionLayout.id, ConstraintSet.LEFT) - } else { - connect( - button.id, - ConstraintSet.LEFT, - motionLayout.getChildAt(index - 1).id, - ConstraintSet.RIGHT, - ) - } if (motionLayout.getChildAt(index + 1) == null) { - connect(button.id, ConstraintSet.RIGHT, motionLayout.id, ConstraintSet.RIGHT) + connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END) } else { connect( button.id, - ConstraintSet.RIGHT, + ConstraintSet.END, motionLayout.getChildAt(index + 1).id, - ConstraintSet.LEFT, + ConstraintSet.START, ) } - connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP) connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM) - clear(button.id, ConstraintSet.START) - clear(button.id, ConstraintSet.END) + + // Index 1 is the first button in the children of motionLayout. + if (index == 1) { + clear(button.id, ConstraintSet.START) + } } private fun ConstraintSet.adjustOpenConstraintsForDrawer( motionLayout: MotionLayout, lastOrientation: Int, ) { - motionLayout.children.forEachIndexed { index, button -> - setAlpha(button.id, 1.0F) - constrainWidth( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) - constrainHeight( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) - when (lastOrientation) { - ORIENTATION_LANDSCAPE -> { - if (index == 0) { - setMargin( - button.id, - ConstraintSet.LEFT, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_left_margin - ), - ) + motionLayout.children.forEachIndexed { index, view -> + if (view.id != R.id.ringer_buttons_background) { + setAlpha(view.id, 1.0F) + constrainWidth( + view.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + constrainHeight( + view.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + when (lastOrientation) { + ORIENTATION_LANDSCAPE -> { + if (index == 1) { + setMargin( + view.id, + ConstraintSet.START, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_margin + ), + ) + } + setButtonPositionLandscapeConstraints(motionLayout, index, view) + if (index != motionLayout.childCount - 1) { + setMargin( + view.id, + ConstraintSet.END, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_components_spacing + ), + ) + } else { + setMargin(view.id, ConstraintSet.END, 0) + } + setMargin(view.id, ConstraintSet.BOTTOM, 0) } - setButtonPositionLandscapeConstraints(motionLayout, index, button) - if (index != motionLayout.childCount - 1) { - setMargin( - button.id, - ConstraintSet.RIGHT, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_components_spacing - ), - ) - } else { - setMargin(button.id, ConstraintSet.RIGHT, 0) + + ORIENTATION_PORTRAIT -> { + if (index == 1) { + setMargin(view.id, ConstraintSet.START, 0) + } + setButtonPositionPortraitConstraints(motionLayout, index, view) + if (index != motionLayout.childCount - 1) { + setMargin( + view.id, + ConstraintSet.BOTTOM, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_components_spacing + ), + ) + } else { + setMargin(view.id, ConstraintSet.BOTTOM, 0) + } + setMargin(view.id, ConstraintSet.END, 0) } - setMargin(button.id, ConstraintSet.BOTTOM, 0) } - ORIENTATION_PORTRAIT -> { - if (index == 0) { - setMargin(button.id, ConstraintSet.LEFT, 0) - } - setButtonPositionPortraitConstraints(motionLayout, index, button) - if (index != motionLayout.childCount - 1) { - setMargin( - button.id, - ConstraintSet.BOTTOM, + } else { + constrainWidth( + view.id, + when (lastOrientation) { + ORIENTATION_LANDSCAPE -> + (motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ) * (motionLayout.childCount - 1)) + + (motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_margin + ) * 2) + + (motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_components_spacing + ) * (motionLayout.childCount - 2)) + + ORIENTATION_PORTRAIT -> motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_components_spacing - ), - ) - } else { - setMargin(button.id, ConstraintSet.BOTTOM, 0) - } - setMargin(button.id, ConstraintSet.RIGHT, 0) - } + R.dimen.volume_dialog_width + ) + + else -> 0 + }, + ) + connect(view.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM) + connect( + view.id, + ConstraintSet.START, + motionLayout.getChildAt(1).id, + ConstraintSet.START, + ) + connect(view.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END) + connect(view.id, ConstraintSet.TOP, motionLayout.getChildAt(1).id, ConstraintSet.TOP) } } } @@ -169,52 +183,91 @@ private fun ConstraintSet.adjustClosedConstraintsForDrawer( selectedIndex: Int, lastOrientation: Int, ) { - motionLayout.children.forEachIndexed { index, button -> - setMargin(button.id, ConstraintSet.RIGHT, 0) - setMargin(button.id, ConstraintSet.BOTTOM, 0) - when (lastOrientation) { - ORIENTATION_LANDSCAPE -> { - setButtonPositionLandscapeConstraints(motionLayout, index, button) - if (selectedIndex != motionLayout.childCount - index - 1) { - setAlpha(button.id, 0.0F) - constrainWidth(button.id, 0) - } else { - setAlpha(button.id, 1.0F) - constrainWidth( - button.id, + motionLayout.children.forEachIndexed { index, view -> + if (view.id != R.id.ringer_buttons_background) { + setMargin(view.id, ConstraintSet.END, 0) + setMargin(view.id, ConstraintSet.BOTTOM, 0) + when (lastOrientation) { + ORIENTATION_LANDSCAPE -> { + setButtonPositionLandscapeConstraints(motionLayout, index, view) + if (selectedIndex != motionLayout.childCount - index - 1) { + setAlpha(view.id, 0.0F) + constrainWidth( + view.id, + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 1F, + motionLayout.context.resources.displayMetrics, + ) + .toInt(), + ) + } else { + connect(view.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END) + setAlpha(view.id, 1.0F) + constrainWidth( + view.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + } + constrainHeight( + view.id, motionLayout.context.resources.getDimensionPixelSize( R.dimen.volume_dialog_ringer_drawer_button_size ), ) } - constrainHeight( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) - } - ORIENTATION_PORTRAIT -> { - setButtonPositionPortraitConstraints(motionLayout, index, button) - if (selectedIndex != motionLayout.childCount - index - 1) { - setAlpha(button.id, 0.0F) - constrainHeight(button.id, 0) - } else { - setAlpha(button.id, 1.0F) - constrainHeight( - button.id, + + ORIENTATION_PORTRAIT -> { + setButtonPositionPortraitConstraints(motionLayout, index, view) + if (selectedIndex != motionLayout.childCount - index - 1) { + setAlpha(view.id, 0.0F) + constrainHeight( + view.id, + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 1F, + motionLayout.context.resources.displayMetrics, + ) + .toInt(), + ) + } else { + setAlpha(view.id, 1.0F) + constrainHeight( + view.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + } + constrainWidth( + view.id, motionLayout.context.resources.getDimensionPixelSize( R.dimen.volume_dialog_ringer_drawer_button_size ), ) } - constrainWidth( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) } + } else { + constrainWidth( + view.id, + motionLayout.context.resources.getDimensionPixelSize(R.dimen.volume_dialog_width), + ) + connect(view.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM) + connect( + view.id, + ConstraintSet.START, + motionLayout.getChildAt(motionLayout.childCount - selectedIndex - 1).id, + ConstraintSet.START, + ) + connect(view.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END) + connect( + view.id, + ConstraintSet.TOP, + motionLayout.getChildAt(motionLayout.childCount - selectedIndex - 1).id, + ConstraintSet.TOP, + ) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java index 238551589361..8c2bdabb2417 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java @@ -832,6 +832,40 @@ public class InternetDialogDelegateLegacyTest extends SysuiTestCase { }); } + @Test + public void updateDialog_shouldUpdateMobileNetworkTrue_updateMobileDataLayout() { + when(mInternetDetailsContentController.isCarrierNetworkActive()).thenReturn(false); + when(mInternetDetailsContentController.isAirplaneModeEnabled()).thenReturn(false); + when(mInternetDetailsContentController.hasActiveSubIdOnDds()).thenReturn(true); + when(mInternetDetailsContentController.activeNetworkIsCellular()).thenReturn(false); + mMobileDataLayout.setVisibility(View.GONE); + + mInternetDialogDelegateLegacy.updateDialog(true); + mBgExecutor.runAllReady(); + + mInternetDialogDelegateLegacy.mDataInternetContent.observe( + mInternetDialogDelegateLegacy.mLifecycleOwner, i -> { + assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE); + }); + } + + @Test + public void updateDialog_shouldUpdateMobileNetworkFalse_doNotUpdateMobileDataLayout() { + when(mInternetDetailsContentController.isCarrierNetworkActive()).thenReturn(false); + when(mInternetDetailsContentController.isAirplaneModeEnabled()).thenReturn(false); + when(mInternetDetailsContentController.hasActiveSubIdOnDds()).thenReturn(true); + when(mInternetDetailsContentController.activeNetworkIsCellular()).thenReturn(false); + mMobileDataLayout.setVisibility(View.GONE); + + mInternetDialogDelegateLegacy.updateDialog(false); + mBgExecutor.runAllReady(); + + mInternetDialogDelegateLegacy.mDataInternetContent.observe( + mInternetDialogDelegateLegacy.mLifecycleOwner, i -> { + assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE); + }); + } + private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible, boolean connectedWifiVisible) { mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 2641070a1a59..b8d26ef53fc7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -69,10 +69,7 @@ var Kosmos.shortcutHelperMultiTaskingShortcutsSource: KeyboardShortcutGroupsSour val Kosmos.shortcutHelperStateRepository by Kosmos.Fixture { ShortcutHelperStateRepository( - fakeCommandQueue, - broadcastDispatcher, fakeInputManager.inputManager, - testScope, testDispatcher, ) } @@ -153,10 +150,20 @@ val Kosmos.customShortcutCategoriesRepository by ) } +val Kosmos.shortcutHelperCoreStartable by + Kosmos.Fixture { + ShortcutHelperCoreStartable( + fakeCommandQueue, + broadcastDispatcher, + shortcutHelperStateRepository, + testScope, + ) + } + val Kosmos.shortcutHelperTestHelper by Kosmos.Fixture { ShortcutHelperTestHelper( - shortcutHelperStateRepository, + shortcutHelperCoreStartable, applicationContext, broadcastDispatcher, fakeCommandQueue, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt index 8b45662e57e6..04eacaafcf91 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt @@ -23,12 +23,13 @@ import android.view.KeyboardShortcutGroup import android.view.WindowManager import android.view.WindowManager.KeyboardShortcutsReceiver import com.android.systemui.broadcast.FakeBroadcastDispatcher +import com.android.systemui.keyboard.shortcut.ShortcutHelperCoreStartable import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever class ShortcutHelperTestHelper( - repo: ShortcutHelperStateRepository, + coreStartable: ShortcutHelperCoreStartable, private val context: Context, private val fakeBroadcastDispatcher: FakeBroadcastDispatcher, private val fakeCommandQueue: FakeCommandQueue, @@ -54,7 +55,7 @@ class ShortcutHelperTestHelper( keyboardShortcutReceiver.onKeyboardShortcutsReceived(currentAppsShortcuts) return@thenAnswer Unit } - repo.start() + coreStartable.start() } /** diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index ad87ceaf6f38..7f0bf0375b4a 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -79,6 +79,13 @@ flag { } flag { + name: "enable_autoclick_indicator" + namespace: "accessibility" + description: "Whether to show autoclick indicator when autoclick feature is enabled." + bug: "383901288" +} + +flag { name: "enable_a11y_checker_logging" namespace: "accessibility" description: "Whether to identify and log app a11y issues." diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java index b095e3e2cff9..50c754008234 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java @@ -304,7 +304,7 @@ public class AutoclickController extends BaseEventStreamTransformation { cacheLastEvent(event, policyFlags, mLastMotionEvent == null || moved /* useAsAnchor */); if (moved) { - rescheduleClick(mDelay); + rescheduleClick(mDelay); } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java index c293087defb6..dc6afe17403d 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java @@ -19,7 +19,9 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.app.appfunctions.AppFunctionManagerConfiguration; import android.content.Context; +import android.content.pm.PackageManagerInternal; +import com.android.server.LocalServices; import com.android.server.SystemService; /** Service that manages app functions. */ @@ -28,7 +30,9 @@ public class AppFunctionManagerService extends SystemService { public AppFunctionManagerService(Context context) { super(context); - mServiceImpl = new AppFunctionManagerServiceImpl(context); + mServiceImpl = + new AppFunctionManagerServiceImpl( + context, LocalServices.getService(PackageManagerInternal.class)); } @Override diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 37276ddac75a..57d33f1a051e 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -50,6 +50,7 @@ import android.app.appsearch.observer.ObserverSpec; import android.app.appsearch.observer.SchemaChangeInfo; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; @@ -87,8 +88,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final Context mContext; private final Map<String, Object> mLocks = new WeakHashMap<>(); private final AppFunctionsLoggerWrapper mLoggerWrapper; + private final PackageManagerInternal mPackageManagerInternal; - public AppFunctionManagerServiceImpl(@NonNull Context context) { + public AppFunctionManagerServiceImpl( + @NonNull Context context, @NonNull PackageManagerInternal packageManagerInternal) { this( context, new RemoteServiceCallerImpl<>( @@ -96,7 +99,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { new CallerValidatorImpl(context), new ServiceHelperImpl(context), new ServiceConfigImpl(), - new AppFunctionsLoggerWrapper(context)); + new AppFunctionsLoggerWrapper(context), + packageManagerInternal); } @VisibleForTesting @@ -106,13 +110,15 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { CallerValidator callerValidator, ServiceHelper appFunctionInternalServiceHelper, ServiceConfig serviceConfig, - AppFunctionsLoggerWrapper loggerWrapper) { + AppFunctionsLoggerWrapper loggerWrapper, + PackageManagerInternal packageManagerInternal) { mContext = Objects.requireNonNull(context); mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller); mCallerValidator = Objects.requireNonNull(callerValidator); mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper); mServiceConfig = serviceConfig; mLoggerWrapper = loggerWrapper; + mPackageManagerInternal = Objects.requireNonNull(packageManagerInternal); } /** Called when the user is unlocked. */ @@ -260,6 +266,24 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { "Cannot find the target service.")); return; } + // Grant target app implicit visibility to the caller + final int grantRecipientUserId = targetUser.getIdentifier(); + final int grantRecipientAppId = + UserHandle.getAppId( + mPackageManagerInternal.getPackageUid( + requestInternal + .getClientRequest() + .getTargetPackageName(), + /* flags= */ 0, + /* userId= */ grantRecipientUserId)); + if (grantRecipientAppId > 0) { + mPackageManagerInternal.grantImplicitAccess( + grantRecipientUserId, + serviceIntent, + grantRecipientAppId, + callingUid, + /* direct= */ true); + } bindAppFunctionServiceUnchecked( requestInternal, serviceIntent, diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceHelperImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceHelperImpl.java index 37a377950a87..071fda4f5d0c 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/ServiceHelperImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceHelperImpl.java @@ -31,8 +31,6 @@ import java.util.Objects; class ServiceHelperImpl implements ServiceHelper { private final Context mContext; - // TODO(b/357551503): Keep track of unlocked users. - ServiceHelperImpl(@NonNull Context context) { mContext = Objects.requireNonNull(context); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 60516c39ffa7..41b4cbd9e074 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -757,9 +757,6 @@ public final class ActiveServices { Message msg = obtainMessage(MSG_BG_START_TIMEOUT); sendMessageAtTime(msg, when); } - if (mStartingBackground.size() < mMaxStartingBackground) { - mAm.backgroundServicesFinishedLocked(mUserId); - } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c9f06ac87269..b536dc524a80 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -14374,10 +14374,6 @@ public class ActivityManagerService extends IActivityManager.Stub mBroadcastController.unbroadcastIntent(caller, intent, userId); } - void backgroundServicesFinishedLocked(int userId) { - mBroadcastQueue.backgroundServicesFinishedLocked(userId); - } - public void finishReceiver(IBinder caller, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, int flags) { mBroadcastController.finishReceiver(caller, resultCode, resultData, resultExtras, @@ -18936,7 +18932,7 @@ public class ActivityManagerService extends IActivityManager.Stub Settings.Global.BROADCAST_BG_CONSTANTS); backConstants.TIMEOUT = BROADCAST_BG_TIMEOUT; - return new BroadcastQueueModernImpl(service, service.mHandler, + return new BroadcastQueueImpl(service, service.mHandler, foreConstants, backConstants); } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index e676b1fca7fb..81d34f67dee9 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -111,7 +111,7 @@ public class BroadcastConstants { public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT; /** - * For {@link BroadcastQueueModernImpl}: Maximum dispatch parallelism + * For {@link BroadcastQueueImpl}: Maximum dispatch parallelism * that we'll tolerate for ordinary broadcast dispatch. */ public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES; @@ -120,7 +120,7 @@ public class BroadcastConstants { ActivityManager.isLowRamDeviceStatic() ? 2 : 4; /** - * For {@link BroadcastQueueModernImpl}: Additional running process queue parallelism beyond + * For {@link BroadcastQueueImpl}: Additional running process queue parallelism beyond * {@link #MAX_RUNNING_PROCESS_QUEUES} for dispatch of "urgent" broadcasts. */ public int EXTRA_RUNNING_URGENT_PROCESS_QUEUES = DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES; @@ -129,7 +129,7 @@ public class BroadcastConstants { private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive urgent + * For {@link BroadcastQueueImpl}: Maximum number of consecutive urgent * broadcast dispatches allowed before letting broadcasts in lower priority queue * to be scheduled in order to avoid starvation. */ @@ -139,7 +139,7 @@ public class BroadcastConstants { private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive normal + * For {@link BroadcastQueueImpl}: Maximum number of consecutive normal * broadcast dispatches allowed before letting broadcasts in lower priority queue * to be scheduled in order to avoid starvation. */ @@ -149,7 +149,7 @@ public class BroadcastConstants { private static final int DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES = 10; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts + * For {@link BroadcastQueueImpl}: Maximum number of active broadcasts * to dispatch to a "running" process queue before we retire them back to * being "runnable" to give other processes a chance to run. */ @@ -160,7 +160,7 @@ public class BroadcastConstants { ActivityManager.isLowRamDeviceStatic() ? 8 : 16; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of active "blocking" broadcasts + * For {@link BroadcastQueueImpl}: Maximum number of active "blocking" broadcasts * to dispatch to a "running" System process queue before we retire them back to * being "runnable" to give other processes a chance to run. Here "blocking" refers to * whether or not we are going to block on the finishReceiver() to be called before moving @@ -173,7 +173,7 @@ public class BroadcastConstants { ActivityManager.isLowRamDeviceStatic() ? 8 : 16; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of active non-"blocking" broadcasts + * For {@link BroadcastQueueImpl}: Maximum number of active non-"blocking" broadcasts * to dispatch to a "running" System process queue before we retire them back to * being "runnable" to give other processes a chance to run. Here "blocking" refers to * whether or not we are going to block on the finishReceiver() to be called before moving @@ -187,7 +187,7 @@ public class BroadcastConstants { ActivityManager.isLowRamDeviceStatic() ? 32 : 64; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of pending + * For {@link BroadcastQueueImpl}: Maximum number of pending * broadcasts to hold for a process before we ignore any delays that policy * might have applied to that process. */ @@ -197,7 +197,7 @@ public class BroadcastConstants { ActivityManager.isLowRamDeviceStatic() ? 128 : 256; /** - * For {@link BroadcastQueueModernImpl}: Delay to apply to normal + * For {@link BroadcastQueueImpl}: Delay to apply to normal * broadcasts, giving a chance for debouncing of rapidly changing events. */ public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS; @@ -205,7 +205,7 @@ public class BroadcastConstants { private static final long DEFAULT_DELAY_NORMAL_MILLIS = +500; /** - * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts + * For {@link BroadcastQueueImpl}: Delay to apply to broadcasts * targeting cached applications. */ public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS; @@ -213,7 +213,7 @@ public class BroadcastConstants { private static final long DEFAULT_DELAY_CACHED_MILLIS = +120_000; /** - * For {@link BroadcastQueueModernImpl}: Delay to apply to urgent + * For {@link BroadcastQueueImpl}: Delay to apply to urgent * broadcasts, typically a negative value to indicate they should be * executed before most other pending broadcasts. */ @@ -222,7 +222,7 @@ public class BroadcastConstants { private static final long DEFAULT_DELAY_URGENT_MILLIS = -120_000; /** - * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts to + * For {@link BroadcastQueueImpl}: Delay to apply to broadcasts to * foreground processes, typically a negative value to indicate they should be * executed before most other pending broadcasts. */ @@ -232,7 +232,7 @@ public class BroadcastConstants { private static final long DEFAULT_DELAY_FOREGROUND_PROC_MILLIS = -120_000; /** - * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts to + * For {@link BroadcastQueueImpl}: Delay to apply to broadcasts to * persistent processes, typically a negative value to indicate they should be * executed before most other pending broadcasts. */ @@ -242,7 +242,7 @@ public class BroadcastConstants { private static final long DEFAULT_DELAY_PERSISTENT_PROC_MILLIS = -120_000; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of complete + * For {@link BroadcastQueueImpl}: Maximum number of complete * historical broadcasts to retain for debugging purposes. */ public int MAX_HISTORY_COMPLETE_SIZE = DEFAULT_MAX_HISTORY_COMPLETE_SIZE; @@ -251,7 +251,7 @@ public class BroadcastConstants { ActivityManager.isLowRamDeviceStatic() ? 64 : 256; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of summarized + * For {@link BroadcastQueueImpl}: Maximum number of summarized * historical broadcasts to retain for debugging purposes. */ public int MAX_HISTORY_SUMMARY_SIZE = DEFAULT_MAX_HISTORY_SUMMARY_SIZE; @@ -268,7 +268,7 @@ public class BroadcastConstants { private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = true; /** - * For {@link BroadcastQueueModernImpl}: How frequently we should check for the pending + * For {@link BroadcastQueueImpl}: How frequently we should check for the pending * cold start validity. */ public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS = @@ -278,7 +278,7 @@ public class BroadcastConstants { private static final long DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30_000; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of outgoing broadcasts from a + * For {@link BroadcastQueueImpl}: Maximum number of outgoing broadcasts from a * freezable process that will be allowed before killing the process. */ public int MAX_FROZEN_OUTGOING_BROADCASTS = DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS; diff --git a/services/core/java/com/android/server/am/BroadcastHistory.java b/services/core/java/com/android/server/am/BroadcastHistory.java index 6ddf60b369e6..700cf9c8deb8 100644 --- a/services/core/java/com/android/server/am/BroadcastHistory.java +++ b/services/core/java/com/android/server/am/BroadcastHistory.java @@ -164,7 +164,7 @@ public class BroadcastHistory { @NeverCompile public boolean dumpLocked(@NonNull PrintWriter pw, @Nullable String dumpPackage, - @Nullable String dumpIntentAction, @NonNull String queueName, + @Nullable String dumpIntentAction, @NonNull SimpleDateFormat sdf, boolean dumpAll) { boolean needSep = true; dumpBroadcastList(pw, sdf, mFrozenBroadcasts, dumpIntentAction, dumpAll, "Frozen"); @@ -198,18 +198,18 @@ public class BroadcastHistory { pw.println(); } needSep = true; - pw.println(" Historical broadcasts [" + queueName + "]:"); + pw.println(" Historical broadcasts:"); printed = true; } if (dumpIntentAction != null) { - pw.print(" Historical Broadcast " + queueName + " #"); + pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); r.dump(pw, " ", sdf); if (!dumpAll) { break; } } else if (dumpAll) { - pw.print(" Historical Broadcast " + queueName + " #"); + pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); r.dump(pw, " ", sdf); } else { @@ -256,7 +256,7 @@ public class BroadcastHistory { pw.println(); } needSep = true; - pw.println(" Historical broadcasts summary [" + queueName + "]:"); + pw.println(" Historical broadcasts summary:"); printed = true; } if (!dumpAll && i >= 50) { diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index ed3cd1ea03c8..db0562f5750a 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -453,7 +453,7 @@ class BroadcastProcessQueue { * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking - * {@link BroadcastQueueModernImpl#updateRunnableList} + * {@link BroadcastQueueImpl#updateRunnableList} */ @CheckResult public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate, @@ -502,7 +502,7 @@ class BroadcastProcessQueue { * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking - * {@link BroadcastQueueModernImpl#updateRunnableList} + * {@link BroadcastQueueImpl#updateRunnableList} */ @CheckResult public boolean setProcessAndUidState(@Nullable ProcessRecord app, boolean uidForeground, @@ -837,7 +837,7 @@ class BroadcastProcessQueue { /** * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking - * {@link BroadcastQueueModernImpl#updateRunnableList} + * {@link BroadcastQueueImpl#updateRunnableList} */ @CheckResult boolean forceDelayBroadcastDelivery(long delayedDurationMs) { @@ -921,7 +921,7 @@ class BroadcastProcessQueue { * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking - * {@link BroadcastQueueModernImpl#updateRunnableList} + * {@link BroadcastQueueImpl#updateRunnableList} */ @CheckResult @VisibleForTesting @@ -945,7 +945,7 @@ class BroadcastProcessQueue { * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking - * {@link BroadcastQueueModernImpl#updateRunnableList} + * {@link BroadcastQueueImpl#updateRunnableList} */ @CheckResult boolean removePrioritizeEarliestRequest() { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index a7d74a9fe54a..5b5ceaae718f 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -49,14 +49,12 @@ public abstract class BroadcastQueue { final @NonNull Handler mHandler; final @NonNull BroadcastSkipPolicy mSkipPolicy; final @NonNull BroadcastHistory mHistory; - final @NonNull String mQueueName; BroadcastQueue(@NonNull ActivityManagerService service, @NonNull Handler handler, - @NonNull String name, @NonNull BroadcastSkipPolicy skipPolicy, + @NonNull BroadcastSkipPolicy skipPolicy, @NonNull BroadcastHistory history) { mService = Objects.requireNonNull(service); mHandler = Objects.requireNonNull(handler); - mQueueName = Objects.requireNonNull(name); mSkipPolicy = Objects.requireNonNull(skipPolicy); mHistory = Objects.requireNonNull(history); } @@ -87,11 +85,6 @@ public abstract class BroadcastQueue { TAG, cookie); } - @Override - public String toString() { - return mQueueName; - } - public abstract void start(@NonNull ContentResolver resolver); /** @@ -129,9 +122,6 @@ public abstract class BroadcastQueue { @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort, boolean waitForServices); - @GuardedBy("mService") - public abstract void backgroundServicesFinishedLocked(int userId); - /** * Signal from OS internals that the given process has just been actively * attached, and is ready to begin receiving broadcasts. @@ -244,8 +234,6 @@ public abstract class BroadcastQueue { /** * Delays delivering broadcasts to the specified package. - * - * <p> Note that this is only valid for modern queue. */ public void forceDelayBroadcastDelivery(@NonNull String targetPackage, long delayedDurationMs) { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.md b/services/core/java/com/android/server/am/BroadcastQueue.md index 81317932ef9b..16c40285dd93 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.md +++ b/services/core/java/com/android/server/am/BroadcastQueue.md @@ -26,7 +26,7 @@ process that isn't currently running. ## Per-process queues -The design of `BroadcastQueueModernImpl` is centered around maintaining a +The design of `BroadcastQueueImpl` is centered around maintaining a separate `BroadcastProcessQueue` instance for each potential process on the device. At this level, a process refers to the `android:process` attributes defined in `AndroidManifest.xml` files, which means it can be defined and @@ -57,7 +57,7 @@ except for those explicitly provided by "ordered" or "prioritized" broadcasts. ## Parallel dispatch Given a collection of per-process queues with valid _runnable at_ timestamps, -BroadcastQueueModernImpl is then willing to promote those _runnable_ queues +BroadcastQueueImpl is then willing to promote those _runnable_ queues into a _running_ state. We choose the next per-process queue to promote based on the sorted ordering of the _runnable at_ timestamps, selecting the longest-waiting process first, which aims to reduce overall broadcast dispatch diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index b270513e5563..36035bdcddbc 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -137,17 +137,17 @@ import java.util.function.Predicate; * {@link #finishReceiverLocked} * </ol> */ -class BroadcastQueueModernImpl extends BroadcastQueue { - BroadcastQueueModernImpl(ActivityManagerService service, Handler handler, +class BroadcastQueueImpl extends BroadcastQueue { + BroadcastQueueImpl(ActivityManagerService service, Handler handler, BroadcastConstants fgConstants, BroadcastConstants bgConstants) { this(service, handler, fgConstants, bgConstants, new BroadcastSkipPolicy(service), new BroadcastHistory(fgConstants)); } - BroadcastQueueModernImpl(ActivityManagerService service, Handler handler, + BroadcastQueueImpl(ActivityManagerService service, Handler handler, BroadcastConstants fgConstants, BroadcastConstants bgConstants, BroadcastSkipPolicy skipPolicy, BroadcastHistory history) { - super(service, handler, "modern", skipPolicy, history); + super(service, handler, skipPolicy, history); // For the moment, read agnostic constants from foreground mConstants = Objects.requireNonNull(fgConstants); @@ -545,8 +545,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - if (DEBUG_BROADCAST) logv("Promoting " + queue - + " from runnable to running; process is " + queue.app); + if (DEBUG_BROADCAST) { + logv("Promoting " + queue + " from runnable to running; process is " + queue.app); + } promoteToRunningLocked(queue); boolean completed; if (processWarm) { @@ -1198,12 +1199,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (receiver instanceof BroadcastFilter) { notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver); thread.scheduleRegisteredReceiver( - ((BroadcastFilter) receiver).receiverList.receiver, - receiverIntent, r.resultCode, r.resultData, r.resultExtras, - r.ordered, r.initialSticky, assumeDelivered, r.userId, - app.mState.getReportedProcState(), - r.shareIdentity ? r.callingUid : Process.INVALID_UID, - r.shareIdentity ? r.callerPackage : null); + ((BroadcastFilter) receiver).receiverList.receiver, + receiverIntent, r.resultCode, r.resultData, r.resultExtras, + r.ordered, r.initialSticky, assumeDelivered, r.userId, + app.mState.getReportedProcState(), + r.shareIdentity ? r.callingUid : Process.INVALID_UID, + r.shareIdentity ? r.callerPackage : null); // TODO: consider making registered receivers of unordered // broadcasts report results to detect ANRs if (assumeDelivered) { @@ -1922,13 +1923,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return getRunningSize() + " running"; } - @GuardedBy("mService") - @Override - public void backgroundServicesFinishedLocked(int userId) { - // Modern queue does not alter the broadcasts delivery behavior based on background - // services, so ignore. - } - private void checkHealth() { synchronized (mService) { checkHealthLocked(); @@ -2418,7 +2412,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @GuardedBy("mService") public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) { long token = proto.start(fieldId); - proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName); mHistory.dumpDebug(proto); proto.end(token); } @@ -2448,7 +2441,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (dumpHistory) { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - needSep = mHistory.dumpLocked(ipw, dumpPackage, dumpIntentAction, mQueueName, + needSep = mHistory.dumpLocked(ipw, dumpPackage, dumpIntentAction, sdf, dumpAll); } return needSep; diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index 1a6051b6ac9c..cc6fabc8fd67 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -21,6 +21,7 @@ per-file FgsTempAllowList.java = file:/ACTIVITY_MANAGER_OWNERS per-file HostingRecord.java = file:/ACTIVITY_MANAGER_OWNERS per-file App*ExitInfo* = file:/ACTIVITY_MANAGER_OWNERS per-file appexitinfo.proto = file:/ACTIVITY_MANAGER_OWNERS +per-file UidObserverController* = file:/ACTIVITY_MANAGER_OWNERS per-file App*StartInfo* = file:/PERFORMANCE_OWNERS per-file appstartinfo.proto = file:/PERFORMANCE_OWNERS diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index bddde9d589f3..2216f2769826 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3212,7 +3212,6 @@ public final class ProcessList { if ((pid > 0 && pid != ActivityManagerService.MY_PID) || (pid == 0 && app.isPendingStart())) { if (pid > 0) { - mService.removePidLocked(pid, app); app.setBindMountPending(false); mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid); @@ -3230,6 +3229,12 @@ public final class ProcessList { } } app.killLocked(reason, reasonCode, subReason, true, async); + if (pid > 0) { + // Remove pid record mapping after killing the process, so there won't be a short + // period that the app is still alive but its access to system may be illegal due + // to no existing record for its pid. + mService.removePidLocked(pid, app); + } mService.handleAppDiedLocked(app, pid, willRestart, allowRestart, false /* fromBinderDied */); if (willRestart) { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index c314ab06fbad..3f915757f137 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -369,16 +369,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void onBootPhase(int phase) { super.onBootPhase(phase); - if (phase == PHASE_ACTIVITY_MANAGER_READY) { - mLockSettingsService.migrateOldDataAfterSystemReady(); - mLockSettingsService.deleteRepairModePersistentDataIfNeeded(); - } else if (phase == PHASE_BOOT_COMPLETED) { - // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old - // build can no longer occur. This is the time to destroy any migrated protectors. - mLockSettingsService.destroyMigratedProtectors(); - - mLockSettingsService.loadEscrowData(); - } + mLockSettingsService.onBootPhase(phase); } @Override @@ -397,6 +388,21 @@ public class LockSettingsService extends ILockSettings.Stub { } } + private void onBootPhase(int phase) { + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { + migrateOldDataAfterSystemReady(); + deleteRepairModePersistentDataIfNeeded(); + } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { + mHandler.post(() -> { + // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old + // build can no longer occur. This is the time to destroy any migrated protectors. + destroyMigratedProtectors(); + + loadEscrowData(); + }); + } + } + @VisibleForTesting protected static class SynchronizedStrongAuthTracker extends LockPatternUtils.StrongAuthTracker { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java index d83d355fce31..b688d4b3ecde 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java @@ -36,6 +36,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.channels.Channel; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; @@ -242,8 +243,10 @@ public class PowerStatsStore { // Lock the directory from access by other JVMs try { - mLockFile.getParentFile().mkdirs(); - mLockFile.createNewFile(); + if (!mLockFile.exists()) { + mLockFile.getParentFile().mkdirs(); + mLockFile.createNewFile(); + } mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock(); } catch (IOException e) { Slog.e(TAG, "Cannot lock snapshot directory", e); @@ -252,10 +255,13 @@ public class PowerStatsStore { private void unlockStoreDirectory() { try { - mJvmLock.close(); + Channel channel = mJvmLock.acquiredBy(); + mJvmLock.release(); + channel.close(); } catch (IOException e) { Slog.e(TAG, "Cannot unlock snapshot directory", e); } finally { + mJvmLock = null; mFileLock.unlock(); } } diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index 5e048810cc97..c8dbbd29823c 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -87,3 +87,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "extended_battery_history_continuous_collection_enabled" + namespace: "backstage_power" + description: "Disable automatic reset of battery stats history on full charge" + bug: "381940953" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index dbe0faf942d9..94830fa11134 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -97,6 +97,14 @@ class BLASTSyncEngine { void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); default void onTransactionCommitTimeout() {} default void onReadyTimeout() {} + + default void onReadyTraceStart(String name, int id) { + Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, name, id); + } + + default void onReadyTraceEnd(String name, int id) { + Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, name, id); + } } /** @@ -149,8 +157,8 @@ class BLASTSyncEngine { } }; if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { - mTraceName = name + "SyncGroupReady"; - Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, mTraceName, id); + mTraceName = name + "-SyncReady#" + id; + listener.onReadyTraceStart(mTraceName, id); } } @@ -209,7 +217,7 @@ class BLASTSyncEngine { private void finishNow() { if (mTraceName != null) { - Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, mTraceName, mSyncId); + mListener.onReadyTraceEnd(mTraceName, mSyncId); } ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId); SurfaceControl.Transaction merged = mWm.mTransactionFactory.get(); @@ -225,9 +233,7 @@ class BLASTSyncEngine { wc.waitForSyncTransactionCommit(wcAwaitingCommit); } - final int syncId = mSyncId; final long mergedTxId = merged.getId(); - final String syncName = mSyncName; class CommitCallback implements Runnable { // Can run a second time if the action completes after the timeout. boolean ran = false; @@ -254,7 +260,7 @@ class BLASTSyncEngine { // a trace. Since these kind of ANRs can trigger such an issue, // try and ensure we will have some visibility in both cases. Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout"); - Slog.e(TAG, "WM sent Transaction (#" + syncId + ", " + syncName + ", tx=" + Slog.e(TAG, "WM sent Transaction (#" + mSyncId + ", " + mSyncName + ", tx=" + mergedTxId + ") to organizer, but never received commit callback." + " Application ANR likely to follow."); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 4c2d8492197c..b9febb83b780 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -578,7 +578,10 @@ public class BackgroundActivityStartController { sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator); sb.append("; balAllowedByPiCreatorWithHardening: ") .append(mBalAllowedByPiCreatorWithHardening); - sb.append("; resultIfPiCreatorAllowsBal: ").append(mResultForCaller); + if (mResultForCaller != null) { + sb.append("; resultIfPiCreatorAllowsBal: ") + .append(balCodeToString(mResultForCaller.mCode)); + } sb.append("; callerStartMode: ").append(balStartModeToString( mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode())); sb.append("; hasRealCaller: ").append(hasRealCaller()); @@ -607,7 +610,10 @@ public class BackgroundActivityStartController { .append(mRealCallerApp.hasActivityInVisibleTask()); } sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender); - sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller); + if (mResultForRealCaller != null) { + sb.append("; resultIfPiSenderAllowsBal: ") + .append(balCodeToString(mResultForRealCaller.mCode)); + } sb.append("; realCallerStartMode: ").append(balStartModeToString( mCheckedOptions.getPendingIntentBackgroundActivityStartMode())); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index a3d71dbc5ed1..e7ed61ae57fb 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -137,9 +137,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "Transition"; private static final String TRACE_NAME_PLAY_TRANSITION = "playing"; - /** The default package for resources */ - private static final String DEFAULT_PACKAGE = "android"; - /** The transition has been created but isn't collecting yet. */ private static final int STATE_PENDING = -1; @@ -3405,6 +3402,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie); } + @Override + public void onReadyTraceStart(String name, int id) { + asyncTraceBegin(name, id); + } + + @Override + public void onReadyTraceEnd(String name, int id) { + asyncTraceEnd(id); + } + boolean hasChanged(WindowContainer wc) { final ChangeInfo chg = mChanges.get(wc); if (chg == null) return false; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index f634beb77329..65cf4ee733dd 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -44,6 +44,7 @@ #include <batteryservice/include/batteryservice/BatteryServiceConstants.h> #include <binder/IServiceManager.h> #include <com_android_input_flags.h> +#include <dispatcher/Entry.h> #include <include/gestures.h> #include <input/Input.h> #include <input/PointerController.h> @@ -66,6 +67,7 @@ #include <atomic> #include <cinttypes> #include <map> +#include <variant> #include <vector> #include "android_hardware_display_DisplayTopology.h" @@ -416,8 +418,9 @@ public: void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source, int32_t action, nsecs_t when, uint32_t& policyFlags) override; - nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent, - uint32_t policyFlags) override; + std::variant<nsecs_t, inputdispatcher::KeyEntry::InterceptKeyResult> + interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent, + uint32_t policyFlags) override; std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent, uint32_t policyFlags) override; void pokeUserActivity(nsecs_t eventTime, int32_t eventType, @@ -1905,9 +1908,9 @@ bool NativeInputManager::isDisplayInteractive(ui::LogicalDisplayId displayId) { return true; } -nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token, - const KeyEvent& keyEvent, - uint32_t policyFlags) { +std::variant<nsecs_t, inputdispatcher::KeyEntry::InterceptKeyResult> +NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token, + const KeyEvent& keyEvent, uint32_t policyFlags) { ATRACE_CALL(); // Policy: // - Ignore untrusted events and pass them along. @@ -1935,7 +1938,19 @@ nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& tok if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching")) { return 0; } - return delayMillis < 0 ? -1 : milliseconds_to_nanoseconds(delayMillis); + + // Negative delay represent states from intercepting the key. + // 0 : Continue event. + if (delayMillis == 0) { + return inputdispatcher::KeyEntry::InterceptKeyResult::CONTINUE; + } + + // -1 : Drop and skip the key event. + if (delayMillis == -1) { + return inputdispatcher::KeyEntry::InterceptKeyResult::SKIP; + } + + return milliseconds_to_nanoseconds(delayMillis); } std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token, diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 018cf20e914f..c62cd6e962b3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -469,8 +469,9 @@ class PermissionService(private val service: AccessCheckingService) : permissionName: String, deviceId: String ): Int { + val pid = Binder.getCallingPid() val uid = Binder.getCallingUid() - val result = context.checkPermission(permissionName, Binder.getCallingPid(), uid) + val result = context.checkPermission(permissionName, pid, uid) if (result == PackageManager.PERMISSION_GRANTED) { return Context.PERMISSION_REQUEST_STATE_GRANTED } @@ -478,17 +479,15 @@ class PermissionService(private val service: AccessCheckingService) : val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val packageState = - packageManagerLocal.withFilteredSnapshot(uid, userId).use { - it.getPackageState(packageName) - } ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE - val androidPackage = packageState.androidPackage - ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + packageManagerLocal.withFilteredSnapshot(uid, userId).use { + it.getPackageState(packageName) + } ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + val androidPackage = + packageState.androidPackage ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE if (appId != packageState.appId) { return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE } - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } + val permission = service.getState { with(policy) { getPermissions()[permissionName] } } if (permission == null || !permission.isRuntime) { return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE } @@ -496,10 +495,37 @@ class PermissionService(private val service: AccessCheckingService) : return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE } - val permissionFlags = service.getState { - getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) + val permissionFlags = + service.getState { + getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) + } + val isUnreqestable = permissionFlags.hasAnyBit(UNREQUESTABLE_MASK) + // Special case for READ_MEDIA_IMAGES due to photo picker + if ((permissionName == Manifest.permission.READ_MEDIA_IMAGES || + permissionName == Manifest.permission.READ_MEDIA_VIDEO) && isUnreqestable) { + val isUserSelectedGranted = + context.checkPermission( + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, + pid, + uid, + ) == PackageManager.PERMISSION_GRANTED + val userSelectedPermissionFlags = + service.getState { + getPermissionFlagsWithPolicy( + appId, + userId, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, + deviceId, + ) + } + if ( + isUserSelectedGranted && + userSelectedPermissionFlags.hasBits(PermissionFlags.USER_FIXED) + ) { + return Context.PERMISSION_REQUEST_STATE_REQUESTABLE + } } - return if (permissionFlags.hasAnyBit(UNREQUESTABLE_MASK)) { + return if (isUnreqestable) { Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE } else { Context.PERMISSION_REQUEST_STATE_REQUESTABLE diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt index a69e9025bfa0..9aaf9ceda93f 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt @@ -18,28 +18,35 @@ package com.android.server.appfunctions import android.app.appfunctions.flags.Flags import android.content.Context +import android.content.pm.PackageManagerInternal import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.CheckFlagsRule import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.server.LocalServices import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @RequiresFlagsEnabled(Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) class AppFunctionManagerServiceImplTest { + @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + @get:Rule - val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java).build() private val context: Context get() = ApplicationProvider.getApplicationContext() - private val serviceImpl = AppFunctionManagerServiceImpl(context) + private val serviceImpl = AppFunctionManagerServiceImpl(context, mock<PackageManagerInternal>()) @Test fun testGetLockForPackage_samePackage() { diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionsLoggingTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionsLoggingTest.kt index 896d2a21d0ac..687acf569d15 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionsLoggingTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionsLoggingTest.kt @@ -25,11 +25,13 @@ import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback import android.app.appsearch.GenericDocument import android.content.Context import android.content.pm.PackageManager +import android.content.pm.PackageManagerInternal import android.os.UserHandle import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.server.LocalServices import com.google.common.util.concurrent.MoreExecutors import org.junit.Before import org.junit.Rule @@ -40,24 +42,25 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever - -/** - * Tests that AppFunctionsStatsLog logs AppFunctionsRequestReported with the expected values. - */ +/** Tests that AppFunctionsStatsLog logs AppFunctionsRequestReported with the expected values. */ @RunWith(AndroidJUnit4::class) class AppFunctionsLoggingTest { @get:Rule val mExtendedMockitoRule: ExtendedMockitoRule = ExtendedMockitoRule.Builder(this) .mockStatic(AppFunctionsStatsLog::class.java) + .mockStatic(LocalServices::class.java) .build() - private val mContext: Context get() = ApplicationProvider.getApplicationContext() + private val mContext: Context + get() = ApplicationProvider.getApplicationContext() + private val mMockPackageManager = mock<PackageManager>() private val mAppFunctionsLoggerWrapper = AppFunctionsLoggerWrapper( mMockPackageManager, MoreExecutors.directExecutor(), - { TEST_CURRENT_TIME_MILLIS }) + { TEST_CURRENT_TIME_MILLIS }, + ) private lateinit var mSafeCallback: SafeOneTimeExecuteAppFunctionCallback private val mServiceImpl = @@ -67,25 +70,40 @@ class AppFunctionsLoggingTest { mock<CallerValidator>(), mock<ServiceHelper>(), ServiceConfigImpl(), - mAppFunctionsLoggerWrapper) + mAppFunctionsLoggerWrapper, + mock<PackageManagerInternal>(), + ) - private val mRequestInternal = ExecuteAppFunctionAidlRequest( - ExecuteAppFunctionRequest.Builder(TEST_TARGET_PACKAGE, TEST_FUNCTION_ID).build(), - UserHandle.CURRENT, TEST_CALLING_PKG, TEST_INITIAL_REQUEST_TIME_MILLIS - ) + private val mRequestInternal = + ExecuteAppFunctionAidlRequest( + ExecuteAppFunctionRequest.Builder(TEST_TARGET_PACKAGE, TEST_FUNCTION_ID).build(), + UserHandle.CURRENT, + TEST_CALLING_PKG, + TEST_INITIAL_REQUEST_TIME_MILLIS, + ) @Before fun setup() { - whenever(mMockPackageManager.getPackageUid(eq(TEST_TARGET_PACKAGE), any<Int>())).thenReturn(TEST_TARGET_UID) - mSafeCallback = mServiceImpl.initializeSafeExecuteAppFunctionCallback(mRequestInternal, mock<IExecuteAppFunctionCallback>(), TEST_CALLING_UID) + whenever(mMockPackageManager.getPackageUid(eq(TEST_TARGET_PACKAGE), any<Int>())) + .thenReturn(TEST_TARGET_UID) + mSafeCallback = + mServiceImpl.initializeSafeExecuteAppFunctionCallback( + mRequestInternal, + mock<IExecuteAppFunctionCallback>(), + TEST_CALLING_UID, + ) mSafeCallback.setExecutionStartTimeAfterBindMillis(TEST_EXECUTION_TIME_AFTER_BIND_MILLIS) } @Test fun testOnSuccess_logsSuccessResponse() { val response = - ExecuteAppFunctionResponse(GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") - .setPropertyLong("longProperty", 42L).setPropertyString("stringProperty", "text").build()) + ExecuteAppFunctionResponse( + GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") + .setPropertyLong("longProperty", 42L) + .setPropertyString("stringProperty", "text") + .build() + ) mSafeCallback.onResult(response) @@ -98,14 +116,16 @@ class AppFunctionsLoggingTest { /* requestSizeBytes= */ eq<Int>(mRequestInternal.clientRequest.requestDataSize), /* responseSizeBytes= */ eq<Int>(response.responseDataSize), /* requestDurationMs= */ eq<Long>(TEST_EXPECTED_E2E_DURATION_MILLIS), - /* requestOverheadMs= */ eq<Long>(TEST_EXPECTED_OVERHEAD_DURATION_MILLIS) + /* requestOverheadMs= */ eq<Long>(TEST_EXPECTED_OVERHEAD_DURATION_MILLIS), ) } } @Test fun testOnError_logsFailureResponse() { - mSafeCallback.onError(AppFunctionException(AppFunctionException.ERROR_DENIED, "Error: permission denied")) + mSafeCallback.onError( + AppFunctionException(AppFunctionException.ERROR_DENIED, "Error: permission denied") + ) ExtendedMockito.verify { AppFunctionsStatsLog.write( @@ -116,7 +136,7 @@ class AppFunctionsLoggingTest { /* requestSizeBytes= */ eq<Int>(mRequestInternal.clientRequest.requestDataSize), /* responseSizeBytes= */ eq<Int>(0), /* requestDurationMs= */ eq<Long>(TEST_EXPECTED_E2E_DURATION_MILLIS), - /* requestOverheadMs= */ eq<Long>(TEST_EXPECTED_OVERHEAD_DURATION_MILLIS) + /* requestOverheadMs= */ eq<Long>(TEST_EXPECTED_OVERHEAD_DURATION_MILLIS), ) } } diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 0d25426700a6..f37ca7cc3fad 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -238,7 +238,7 @@ test_module_config { include_filters: [ "com.android.server.am.BroadcastQueueTest", "com.android.server.am.BroadcastRecordTest", - "com.android.server.am.BroadcastQueueModernImplTest", + "com.android.server.am.BroadcastQueueImplTest", ], } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java index 82237bca2e34..1e665c2c5c50 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java @@ -96,8 +96,8 @@ import java.util.List; import java.util.Objects; @SmallTest -public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest { - private static final String TAG = "BroadcastQueueModernImplTest"; +public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest { + private static final String TAG = "BroadcastQueueImplTest"; private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID; private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1; @@ -109,7 +109,7 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest { @Mock BroadcastProcessQueue mQueue3; @Mock BroadcastProcessQueue mQueue4; - BroadcastQueueModernImpl mImpl; + BroadcastQueueImpl mImpl; BroadcastProcessQueue mHead; @@ -121,7 +121,7 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest { mConstants.DELAY_NORMAL_MILLIS = 10_000; mConstants.DELAY_CACHED_MILLIS = 120_000; - mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), + mImpl = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), mConstants, mConstants, mSkipPolicy, mEmptyHistory); mAms.setBroadcastQueueForTest(mImpl); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index db6aeebecce7..ad35b25a0d74 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -233,7 +233,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { }).when(mAms).registerUidObserver(any(), anyInt(), eq(ActivityManager.PROCESS_STATE_TOP), any()); - mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), + mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), mConstants, mConstants, mSkipPolicy, mEmptyHistory); mAms.setBroadcastQueueForTest(mQueue); mQueue.start(mContext.getContentResolver()); @@ -454,7 +454,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { private void assertHealth() { // If this fails, it'll throw a clear reason message - ((BroadcastQueueModernImpl) mQueue).assertHealthLocked(); + ((BroadcastQueueImpl) mQueue).assertHealthLocked(); } private static Map<String, Object> asMap(Bundle bundle) { @@ -1953,7 +1953,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { withPriority(receiverGreenA, 5)))); waitForIdle(); - // In the modern queue, we don't end up replacing the old broadcast to + // In the broadcast queue, we don't end up replacing the old broadcast to // avoid creating priority inversion and so the process will receive // both the old and new broadcasts. verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane); @@ -2235,7 +2235,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { } waitForIdle(); - // Modern stack requests once each time we promote a process to + // The broadcast queue requests once each time we promote a process to // running; we promote "green" twice, and "blue" and "yellow" once final int expectedTimes = 4; verify(mAms, times(expectedTimes)) 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 bc81feb3f7c7..164eec6fbc49 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 @@ -34,6 +34,8 @@ import android.os.Parcel; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.NetworkRegistrationInfo; import android.util.AtomicFile; import android.util.Log; @@ -46,6 +48,7 @@ import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerStats; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; @@ -61,14 +64,22 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.TimeZone; /** * Test BatteryStatsHistory. */ @RunWith(AndroidJUnit4.class) +@EnableFlags({com.android.server.power.optimization.Flags + .FLAG_EXTENDED_BATTERY_HISTORY_CONTINUOUS_COLLECTION_ENABLED}) public class BatteryStatsHistoryTest { private static final String TAG = "BatteryStatsHistoryTest"; + private static final int MAX_HISTORY_BUFFER_SIZE = 1024; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final Parcel mHistoryBuffer = Parcel.obtain(); private File mSystemDir; private File mHistoryDir; @@ -98,15 +109,18 @@ public class BatteryStatsHistoryTest { mHistoryDir.delete(); mClock.realtime = 123; + mClock.currentTime = 1743645660000L; // 2025-04-03, 2:01:00 AM mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32768, MAX_HISTORY_BUFFER_SIZE, mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, mEventLogger); + mHistory.forceRecordAllHistory(); + mHistory.startRecordingHistory(mClock.realtime, mClock.uptime, false); when(mStepDetailsCalculator.getHistoryStepDetails()) .thenReturn(new BatteryStats.HistoryStepDetails()); - mHistoryPrinter = new BatteryStats.HistoryPrinter(); + mHistoryPrinter = new BatteryStats.HistoryPrinter(TimeZone.getTimeZone("GMT")); } @Test @@ -145,8 +159,6 @@ public class BatteryStatsHistoryTest { @Test public void testAtraceExcludedState() { - mHistory.forceRecordAllHistory(); - Mockito.when(mTracer.tracingEnabled()).thenReturn(true); mHistory.recordStateStartEvent(mClock.elapsedRealtime(), @@ -354,8 +366,6 @@ public class BatteryStatsHistoryTest { } private void prepareMultiFileHistory() { - mHistory.forceRecordAllHistory(); - mClock.realtime = 1000; mClock.uptime = 1000; mHistory.recordEvent(mClock.realtime, mClock.uptime, @@ -428,7 +438,8 @@ public class BatteryStatsHistoryTest { powerStats.uidStats.put(300, new long[]{400, 500}); powerStats.uidStats.put(600, new long[]{700, 800}); - mHistory.recordPowerStats(200, 200, powerStats); + mClock.advance(200); + mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); BatteryStatsHistoryIterator iterator = mHistory.iterate(0, MonotonicClock.UNDEFINED); BatteryStats.HistoryItem item; @@ -437,7 +448,7 @@ public class BatteryStatsHistoryTest { assertThat(item = iterator.next()).isNotNull(); String dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+200ms"); + assertThat(dump).contains("04-03 02:01:00.200"); assertThat(dump).contains("duration=100"); assertThat(dump).contains("foo=[200]"); assertThat(dump).contains("300: [400, 500]"); @@ -446,49 +457,49 @@ public class BatteryStatsHistoryTest { @Test public void testNrState_dump() { - mHistory.forceRecordAllHistory(); - mHistory.startRecordingHistory(0, 0, /* reset */ true); mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80, 1234); - mHistory.recordNrStateChangeEvent(200, 200, + mClock.advance(200); + mHistory.recordNrStateChangeEvent(mClock.realtime, mClock.uptime, NetworkRegistrationInfo.NR_STATE_RESTRICTED); - mHistory.recordNrStateChangeEvent(300, 300, + mClock.advance(100); + mHistory.recordNrStateChangeEvent(mClock.realtime, mClock.uptime, NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED); - mHistory.recordNrStateChangeEvent(400, 400, + mClock.advance(100); + mHistory.recordNrStateChangeEvent(mClock.realtime, mClock.uptime, NetworkRegistrationInfo.NR_STATE_CONNECTED); - mHistory.recordNrStateChangeEvent(500, 500, + mClock.advance(100); + mHistory.recordNrStateChangeEvent(mClock.realtime, mClock.uptime, NetworkRegistrationInfo.NR_STATE_NONE); BatteryStatsHistoryIterator iterator = mHistory.iterate(0, MonotonicClock.UNDEFINED); - BatteryStats.HistoryItem item = new BatteryStats.HistoryItem(); + BatteryStats.HistoryItem item; assertThat(item = iterator.next()).isNotNull(); // First item contains current time only assertThat(item = iterator.next()).isNotNull(); String dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+200ms"); + assertThat(dump).contains("04-03 02:01:00.200"); assertThat(dump).contains("nr_state=restricted"); assertThat(item = iterator.next()).isNotNull(); dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+300ms"); + assertThat(dump).contains("04-03 02:01:00.300"); assertThat(dump).contains("nr_state=not_restricted"); assertThat(item = iterator.next()).isNotNull(); dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+400ms"); + assertThat(dump).contains("04-03 02:01:00.400"); assertThat(dump).contains("nr_state=connected"); assertThat(item = iterator.next()).isNotNull(); dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+500ms"); + assertThat(dump).contains("04-03 02:01:00.500"); assertThat(dump).contains("nr_state=none"); } @Test public void testNrState_checkin() { - mHistory.forceRecordAllHistory(); - mHistory.startRecordingHistory(0, 0, /* reset */ true); mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80, 1234); @@ -502,7 +513,7 @@ public class BatteryStatsHistoryTest { NetworkRegistrationInfo.NR_STATE_NONE); BatteryStatsHistoryIterator iterator = mHistory.iterate(0, MonotonicClock.UNDEFINED); - BatteryStats.HistoryItem item = new BatteryStats.HistoryItem(); + BatteryStats.HistoryItem item; assertThat(item = iterator.next()).isNotNull(); // First item contains current time only assertThat(item = iterator.next()).isNotNull(); @@ -633,10 +644,17 @@ public class BatteryStatsHistoryTest { @Test public void recordProcStateChange() { - mHistory.recordProcessStateChange(200, 200, 42, BatteryConsumer.PROCESS_STATE_BACKGROUND); - mHistory.recordProcessStateChange(300, 300, 42, BatteryConsumer.PROCESS_STATE_FOREGROUND); + mClock.advance(200); + mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, 42, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + + mClock.advance(100); + mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, 42, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + + mClock.advance(100); // Large UID, > 0xFFFFFF - mHistory.recordProcessStateChange(400, 400, + mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, UserHandle.getUid(777, Process.LAST_ISOLATED_UID), BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); @@ -647,17 +665,17 @@ public class BatteryStatsHistoryTest { assertThat(item = iterator.next()).isNotNull(); String dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+200ms"); + assertThat(dump).contains("04-03 02:01:00.200"); assertThat(dump).contains("procstate: 42: bg"); assertThat(item = iterator.next()).isNotNull(); dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+300ms"); + assertThat(dump).contains("04-03 02:01:00.300"); assertThat(dump).contains("procstate: 42: fg"); assertThat(item = iterator.next()).isNotNull(); dump = toString(item, /* checkin */ false); - assertThat(dump).contains("+400ms"); + assertThat(dump).contains("04-03 02:01:00.400"); assertThat(dump).contains("procstate: u777i999: fgs"); } @@ -672,7 +690,6 @@ public class BatteryStatsHistoryTest { @Test public void getMonotonicHistorySize() { long lastHistorySize = mHistory.getMonotonicHistorySize(); - mHistory.forceRecordAllHistory(); mClock.realtime = 1000; mClock.uptime = 1000; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java index 5e57cc36797b..215ac409007e 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java @@ -40,4 +40,13 @@ public class MockClock extends Clock { public long currentTimeMillis() { return currentTime; } + + /** + * Advances the clock by the given number of milliseconds. + */ + public void advance(long milliseconds) { + realtime += milliseconds; + uptime += milliseconds; + currentTime += milliseconds; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java index 854bda03f18d..51706d72cb35 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java @@ -572,7 +572,6 @@ public class BackgroundActivityStartControllerTests { + "inVisibleTask: false; " + "balAllowedByPiCreator: BSP.ALLOW_BAL; " + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; " - + "resultIfPiCreatorAllowsBal: null; " + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "hasRealCaller: true; " + "isCallForResult: false; " @@ -589,7 +588,6 @@ public class BackgroundActivityStartControllerTests { + "originatingPendingIntent: null; " + "realCallerApp: null; " + "balAllowedByPiSender: BSP.ALLOW_BAL; " - + "resultIfPiSenderAllowsBal: null; " + "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "balRequireOptInByPendingIntentCreator: true; " + "balDontBringExistingBackgroundTaskStackToFg: true]"); @@ -677,7 +675,6 @@ public class BackgroundActivityStartControllerTests { + "inVisibleTask: false; " + "balAllowedByPiCreator: BSP.NONE; " + "balAllowedByPiCreatorWithHardening: BSP.NONE; " - + "resultIfPiCreatorAllowsBal: null; " + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "hasRealCaller: true; " + "isCallForResult: false; " @@ -694,7 +691,6 @@ public class BackgroundActivityStartControllerTests { + "originatingPendingIntent: PendingIntentRecord; " + "realCallerApp: null; " + "balAllowedByPiSender: BSP.ALLOW_FGS; " - + "resultIfPiSenderAllowsBal: null; " + "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "balRequireOptInByPendingIntentCreator: true; " + "balDontBringExistingBackgroundTaskStackToFg: true]"); |