diff options
92 files changed, 2133 insertions, 638 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 7ed1a4382054..ea2ae0c9e15f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -275,6 +275,7 @@ package android { public static final class R.attr { ctor public R.attr(); field public static final int absListViewStyle = 16842858; // 0x101006a + field public static final int accessibilityDataPrivate; field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 field public static final int accessibilityFlags = 16843652; // 0x1010384 @@ -49955,6 +49956,7 @@ package android.view { method public void invalidate(); method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable); method public void invalidateOutline(); + method public boolean isAccessibilityDataPrivate(); method public boolean isAccessibilityFocused(); method public boolean isAccessibilityHeading(); method public boolean isActivated(); @@ -50132,6 +50134,7 @@ package android.view { method public void scrollTo(int, int); method public void sendAccessibilityEvent(int); method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent); + method public void setAccessibilityDataPrivate(int); method public void setAccessibilityDelegate(@Nullable android.view.View.AccessibilityDelegate); method public void setAccessibilityHeading(boolean); method public void setAccessibilityLiveRegion(int); @@ -50312,6 +50315,9 @@ package android.view { method @CallSuper protected boolean verifyDrawable(@NonNull android.graphics.drawable.Drawable); method @Deprecated public boolean willNotCacheDrawing(); method public boolean willNotDraw(); + field public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0; // 0x0 + field public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 2; // 0x2 + field public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 1; // 0x1 field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2 field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 @@ -51764,9 +51770,11 @@ package android.view.accessibility { method public int getSpeechStateChangeTypes(); method public int getWindowChanges(); method public void initFromParcel(android.os.Parcel); + method public boolean isAccessibilityDataPrivate(); method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int); method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent); method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(); + method public void setAccessibilityDataPrivate(boolean); method public void setAction(int); method public void setContentChangeTypes(int); method public void setEventTime(long); @@ -51857,6 +51865,7 @@ package android.view.accessibility { method public static boolean isAccessibilityButtonSupported(); method public boolean isAudioDescriptionRequested(); method public boolean isEnabled(); + method public boolean isRequestFromAccessibilityTool(); method public boolean isTouchExplorationEnabled(); method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer); method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index ddbccfa8f404..6c941e751b9e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -90,6 +90,7 @@ package android.accessibilityservice { public class AccessibilityServiceInfo implements android.os.Parcelable { method @NonNull public android.content.ComponentName getComponentName(); + method public void setAccessibilityTool(boolean); } } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 2e89ce83cd36..8f6bfd3b13db 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -784,6 +784,7 @@ public class AccessibilityServiceInfo implements Parcelable { mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout; mInteractiveUiTimeout = other.mInteractiveUiTimeout; flags = other.flags; + mIsAccessibilityTool = other.mIsAccessibilityTool; } private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) { @@ -1112,6 +1113,26 @@ public class AccessibilityServiceInfo implements Parcelable { } /** + * Sets whether the service is used to assist users with disabilities. + * + * <p> + * This property is normally provided in the service's {@link #mResolveInfo ResolveInfo}. + * </p> + * + * <p> + * This method is helpful for unit testing. However, this property is not dynamically + * configurable by a standard {@link AccessibilityService} so it's not possible to update the + * copy held by the system with this method. + * </p> + * + * @hide + */ + @TestApi + public void setAccessibilityTool(boolean isAccessibilityTool) { + mIsAccessibilityTool = isAccessibilityTool; + } + + /** * Indicates if the service is used to assist users with disabilities. * * @return {@code true} if the property is set to true. diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index a045157e02db..8d57e32a763c 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -550,6 +550,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS); + info.setAccessibilityTool(true); try { // Calling out with a lock held is fine since if the system // process is gone the client calling in will be killed. diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java index 47bec618cfd9..24fe0dbe5760 100644 --- a/core/java/android/attention/AttentionManagerInternal.java +++ b/core/java/android/attention/AttentionManagerInternal.java @@ -83,11 +83,11 @@ public abstract class AttentionManagerInternal { } /** Internal interface for proximity callback. */ - public abstract static class ProximityUpdateCallbackInternal { + public interface ProximityUpdateCallbackInternal { /** * @param distance the estimated distance of the user (in meter) * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. */ - public abstract void onProximityUpdate(double distance); + void onProximityUpdate(double distance); } } diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java index e088f306cd98..068df22e222b 100644 --- a/core/java/android/os/AggregateBatteryConsumer.java +++ b/core/java/android/os/AggregateBatteryConsumer.java @@ -19,6 +19,7 @@ package android.os; import android.annotation.NonNull; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; +import android.util.proto.ProtoOutputStream; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -97,6 +98,19 @@ public final class AggregateBatteryConsumer extends BatteryConsumer { } } + void writePowerComponentModelProto(@NonNull ProtoOutputStream proto) { + for (int i = 0; i < POWER_COMPONENT_COUNT; i++) { + final int powerModel = getPowerModel(i); + if (powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) continue; + + final long token = proto.start(BatteryUsageStatsAtomsProto.COMPONENT_MODELS); + proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.COMPONENT, i); + proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_MODEL, + powerModelToProtoEnum(powerModel)); + proto.end(token); + } + } + /** * Builder for DeviceBatteryConsumer. */ diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index f4ca1b50fe86..de2dec779c99 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -479,6 +479,21 @@ public abstract class BatteryConsumer { } /** + * Returns the equivalent PowerModel enum for the specified power model. + * {@see BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage.PowerModel} + */ + public static int powerModelToProtoEnum(@BatteryConsumer.PowerModel int powerModel) { + switch (powerModel) { + case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: + return BatteryUsageStatsAtomsProto.PowerComponentModel.MEASURED_ENERGY; + case BatteryConsumer.POWER_MODEL_POWER_PROFILE: + return BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_PROFILE; + default: + return BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED; + } + } + + /** * Returns the name of the specified process state. Intended for logging and debugging. */ public static String processStateToString(@BatteryConsumer.ProcessState int processState) { diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index b76592d4be58..7105b1ad8b38 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -276,7 +276,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { /** * Returns a battery consumer for the specified battery consumer type. */ - public BatteryConsumer getAggregateBatteryConsumer( + public AggregateBatteryConsumer getAggregateBatteryConsumer( @AggregateBatteryConsumerScope int scope) { return mAggregateBatteryConsumers[scope]; } @@ -450,7 +450,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { @NonNull private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) { - final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer( + final AggregateBatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer( AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp()); @@ -462,6 +462,9 @@ public final class BatteryUsageStats implements Parcelable, Closeable { getDischargeDurationMs()); deviceBatteryConsumer.writeStatsProto(proto, BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER); + if (mIncludesPowerModels) { + deviceBatteryConsumer.writePowerComponentModelProto(proto); + } writeUidBatteryConsumersProto(proto, maxRawSize); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c802e56c8dc7..58162f6b5abe 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -90,6 +90,7 @@ import java.util.Set; public class UserManager { private static final String TAG = "UserManager"; + private static final boolean VERBOSE = false; @UnsupportedAppUsage private final IUserManager mService; @@ -2056,10 +2057,10 @@ public class UserManager { final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY); switch (emulatedMode) { case SYSTEM_USER_MODE_EMULATION_FULL: - Log.d(TAG, "isHeadlessSystemUserMode(): emulating as false"); + if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false"); return false; case SYSTEM_USER_MODE_EMULATION_HEADLESS: - Log.d(TAG, "isHeadlessSystemUserMode(): emulating as true"); + if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true"); return true; case SYSTEM_USER_MODE_EMULATION_DEFAULT: case "": // property not set diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 07e48acef074..05b3925fcb9f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14264,7 +14264,7 @@ public final class Settings { * * @hide */ - public static final int DEFAULT_ENABLE_TARE = 0; + public static final int DEFAULT_ENABLE_TARE = 1; /** * Whether to enable the TARE AlarmManager economic policy or not. @@ -17762,6 +17762,37 @@ public final class Settings { * @hide */ public static final String CHARGING_SOUNDS_ENABLED = "wear_charging_sounds_enabled"; + + /** The status of the early updates process. + * @hide + */ + public static final String EARLY_UPDATES_STATUS = "early_updates_status"; + + /** + * Early updates not started + * @hide + */ + public static final int EARLY_UPDATES_STATUS_NOT_STARTED = 0; + /** + * Early updates started and in progress + * @hide + */ + public static final int EARLY_UPDATES_STATUS_STARTED = 1; + /** + * Early updates completed and was successful + * @hide + */ + public static final int EARLY_UPDATES_STATUS_SUCCESS = 2; + /** + * Early updates skipped + * @hide + */ + public static final int EARLY_UPDATES_STATUS_SKIPPED = 3; + /** + * Early updates aborted due to timeout + * @hide + */ + public static final int EARLY_UPDATES_STATUS_ABORTED = 4; } } diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index 72341453a1f4..ab71459ed51e 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -95,6 +95,16 @@ public final class HotwordDetectedResult implements Parcelable { /** Limits the max value for the triggered audio channel. */ private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63; + /** + * The bundle key for proximity value + * + * TODO(b/238896013): Move the proximity logic out of bundle to proper API. + * + * @hide + */ + public static final String EXTRA_PROXIMITY_METERS = + "android.service.voice.extra.PROXIMITY_METERS"; + /** Confidence level in the trigger outcome. */ @HotwordConfidenceLevelValue private final int mConfidenceLevel; @@ -197,6 +207,14 @@ public final class HotwordDetectedResult implements Parcelable { * <p>The use of this method is discouraged, and support for it will be removed in future * versions of Android. * + * <p>After the trigger happens, a special case of proximity-related extra, with the key of + * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double), + * will be stored to enable proximity logic. The proximity meters is provided by the system, + * on devices that support detecting proximity of nearby users, to help disambiguate which + * nearby device should respond. When the proximity is unknown, the proximity value will not + * be stored. This mapping will be excluded from the max bundle size calculation because this + * mapping is included after the result is returned from the hotword detector service. + * * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. */ @@ -315,8 +333,23 @@ public final class HotwordDetectedResult implements Parcelable { "audioChannel"); } if (!mExtras.isEmpty()) { - Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, getMaxBundleSize(), - "extras"); + // Remove the proximity key from the bundle before checking the bundle size. The + // proximity value is added after the privileged module and can avoid the + // maxBundleSize limitation. + if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) { + double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS); + mExtras.remove(EXTRA_PROXIMITY_METERS); + // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle + // has parcelable size of 4, but the default bundle has parcelable size of 0. + if (mExtras.size() > 0) { + Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, + getMaxBundleSize(), "extras"); + } + mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters); + } else { + Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, + getMaxBundleSize(), "extras"); + } } } @@ -513,6 +546,14 @@ public final class HotwordDetectedResult implements Parcelable { * <p>The use of this method is discouraged, and support for it will be removed in future * versions of Android. * + * <p>After the trigger happens, a special case of proximity-related extra, with the key of + * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double), + * will be stored to enable proximity logic. The proximity meters is provided by the system, + * on devices that support detecting proximity of nearby users, to help disambiguate which + * nearby device should respond. When the proximity is unknown, the proximity value will not + * be stored. This mapping will be excluded from the max bundle size calculation because this + * mapping is included after the result is returned from the hotword detector service. + * * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. */ @@ -813,6 +854,14 @@ public final class HotwordDetectedResult implements Parcelable { * <p>The use of this method is discouraged, and support for it will be removed in future * versions of Android. * + * <p>After the trigger happens, a special case of proximity-related extra, with the key of + * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double), + * will be stored to enable proximity logic. The proximity meters is provided by the system, + * on devices that support detecting proximity of nearby users, to help disambiguate which + * nearby device should respond. When the proximity is unknown, the proximity value will not + * be stored. This mapping will be excluded from the max bundle size calculation because this + * mapping is included after the result is returned from the hotword detector service. + * * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. */ @@ -882,10 +931,10 @@ public final class HotwordDetectedResult implements Parcelable { } @DataClass.Generated( - time = 1625541522353L, + time = 1658357814396L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java", - inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index a6f63e859049..52dc34298ae7 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -89,9 +89,7 @@ public final class AccessibilityInteractionController { // Callbacks should have the same configuration of the flags below to allow satisfying a pending // node request on prefetch - private static final int FLAGS_AFFECTING_REPORTED_DATA = - AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS - | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; + private static final int FLAGS_AFFECTING_REPORTED_DATA = AccessibilityNodeInfo.FLAG_REPORT_MASK; private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); @@ -167,6 +165,11 @@ public final class AccessibilityInteractionController { return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown()); } + private boolean isVisibleToAccessibilityService(View view) { + return view != null && (!view.isAccessibilityDataPrivate() + || mA11yManager.isRequestFromAccessibilityTool()); + } + public void findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, @@ -358,7 +361,7 @@ public final class AccessibilityInteractionController { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; + setAccessibilityFetchFlags(flags); requestedView = findViewByAccessibilityId(accessibilityViewId); if (requestedView != null && isShown(requestedView)) { requestedNode = populateAccessibilityNodeInfoForView( @@ -371,7 +374,7 @@ public final class AccessibilityInteractionController { mPrefetcher.prefetchAccessibilityNodeInfos(requestedView, requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos); - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); } } } finally { @@ -396,7 +399,7 @@ public final class AccessibilityInteractionController { } mPrefetcher.prefetchAccessibilityNodeInfos(requestedView, requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos); - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion); final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos, @@ -478,7 +481,7 @@ public final class AccessibilityInteractionController { || viewId == null) { return; } - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; + setAccessibilityFetchFlags(flags); final View root = findViewByAccessibilityId(accessibilityViewId); if (root != null) { final int resolvedViewId = root.getContext().getResources() @@ -494,7 +497,7 @@ public final class AccessibilityInteractionController { mAddNodeInfosForViewId.reset(); } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, matrixValues, interactiveRegion); } @@ -542,7 +545,7 @@ public final class AccessibilityInteractionController { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; + setAccessibilityFetchFlags(flags); final View root = findViewByAccessibilityId(accessibilityViewId); if (root != null && isShown(root)) { AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); @@ -561,7 +564,7 @@ public final class AccessibilityInteractionController { final int viewCount = foundViews.size(); for (int i = 0; i < viewCount; i++) { View foundView = foundViews.get(i); - if (isShown(foundView)) { + if (isShown(foundView) && isVisibleToAccessibilityService(foundView)) { provider = foundView.getAccessibilityNodeProvider(); if (provider != null) { List<AccessibilityNodeInfo> infosFromProvider = @@ -579,7 +582,7 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, matrixValues, interactiveRegion); } @@ -627,7 +630,7 @@ public final class AccessibilityInteractionController { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; + setAccessibilityFetchFlags(flags); final View root = findViewByAccessibilityId(accessibilityViewId); if (root != null && isShown(root)) { switch (focusType) { @@ -642,6 +645,9 @@ public final class AccessibilityInteractionController { if (!isShown(host)) { break; } + if (!isVisibleToAccessibilityService(host)) { + break; + } // If the host has a provider ask this provider to search for the // focus instead fetching all provider nodes to do the search here. AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); @@ -662,6 +668,9 @@ public final class AccessibilityInteractionController { if (!isShown(target)) { break; } + if (!isVisibleToAccessibilityService(target)) { + break; + } AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); if (provider != null) { focused = provider.findFocus(focusType); @@ -675,7 +684,7 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); updateInfoForViewportAndReturnFindNodeResult( focused, callback, interactionId, spec, matrixValues, interactiveRegion); } @@ -722,7 +731,7 @@ public final class AccessibilityInteractionController { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; + setAccessibilityFetchFlags(flags); final View root = findViewByAccessibilityId(accessibilityViewId); if (root != null && isShown(root)) { View nextView = root.focusSearch(direction); @@ -731,7 +740,7 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); updateInfoForViewportAndReturnFindNodeResult( next, callback, interactionId, spec, matrixValues, interactiveRegion); } @@ -778,9 +787,9 @@ public final class AccessibilityInteractionController { mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) { return; } - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; + setAccessibilityFetchFlags(flags); final View target = findViewByAccessibilityId(accessibilityViewId); - if (target != null && isShown(target)) { + if (target != null && isShown(target) && isVisibleToAccessibilityService(target)) { mA11yManager.notifyPerformingAction(action); if (action == R.id.accessibilityActionClickOnClickableSpan) { // Handle this hidden action separately @@ -799,7 +808,7 @@ public final class AccessibilityInteractionController { } } finally { try { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); callback.setPerformAccessibilityActionResult(succeeded, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -823,9 +832,9 @@ public final class AccessibilityInteractionController { return; } try { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = - AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; - final View root = mViewRootImpl.mView; + setAccessibilityFetchFlags( + AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS); + final View root = getRootView(); if (root != null && isShown(root)) { final View host = mViewRootImpl.mAccessibilityFocusedHost; // If there is no accessibility focus host or it is not a descendant @@ -849,7 +858,7 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + resetAccessibilityFetchFlags(); } } @@ -869,7 +878,7 @@ public final class AccessibilityInteractionController { || mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) { return; } - final View root = mViewRootImpl.mView; + final View root = getRootView(); if (root != null && isShown(root)) { // trigger ACTION_OUTSIDE to notify windows final long now = SystemClock.uptimeMillis(); @@ -882,12 +891,30 @@ public final class AccessibilityInteractionController { private View findViewByAccessibilityId(int accessibilityId) { if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) { - return mViewRootImpl.mView; + return getRootView(); } else { return AccessibilityNodeIdManager.getInstance().findView(accessibilityId); } } + private View getRootView() { + if (!isVisibleToAccessibilityService(mViewRootImpl.mView)) { + return null; + } + return mViewRootImpl.mView; + } + + private void setAccessibilityFetchFlags(int flags) { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; + mA11yManager.setRequestFromAccessibilityTool( + (flags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) != 0); + } + + private void resetAccessibilityFetchFlags() { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + mA11yManager.setRequestFromAccessibilityTool(false); + } + // The boundInScreen includes magnification effect, so we need to normalize it before // determine the visibility. private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, @@ -1706,7 +1733,7 @@ public final class AccessibilityInteractionController { @Override public boolean test(View view) { - if (view.getId() == mViewId && isShown(view)) { + if (view.getId() == mViewId && isShown(view) && isVisibleToAccessibilityService(view)) { mInfos.add(view.createAccessibilityNodeInfo()); } return false; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 48937770eddb..84edb3a7bdee 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3085,6 +3085,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO; /** + * Automatically determine whether the view should only allow interactions from + * {@link android.accessibilityservice.AccessibilityService}s with the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + * + * <p> + * Accessibility interactions from services without {@code isAccessibilityTool} set to true are + * disallowed for any of the following conditions: + * <li>this view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.</li> + * <li>this view sets {@link #getFilterTouchesWhenObscured()}.</li> + * <li>any parent of this view returns true from {@link #isAccessibilityDataPrivate()}.</li> + * </p> + */ + public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0x00000000; + + /** + * Only allow interactions from {@link android.accessibilityservice.AccessibilityService}s + * with the {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} + * property set to true. + */ + public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 0x00000001; + + /** + * Allow interactions from all {@link android.accessibilityservice.AccessibilityService}s, + * regardless of their + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property. + */ + public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 0x00000002; + + /** @hide */ + @IntDef(prefix = { "ACCESSIBILITY_DATA_PRIVATE_" }, value = { + ACCESSIBILITY_DATA_PRIVATE_AUTO, + ACCESSIBILITY_DATA_PRIVATE_YES, + ACCESSIBILITY_DATA_PRIVATE_NO, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AccessibilityDataPrivate {} + + /** * Mask for obtaining the bits which specify how to determine * whether a view is important for accessibility. */ @@ -4527,6 +4566,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CharSequence mAccessibilityPaneTitle; /** + * Describes whether this view should only allow interactions from + * {@link android.accessibilityservice.AccessibilityService}s with the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + */ + private int mAccessibilityDataPrivate = ACCESSIBILITY_DATA_PRIVATE_AUTO; + + /** * Specifies the id of a view for which this view serves as a label for * accessibility purposes. */ @@ -5919,6 +5966,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setImportantForAccessibility(a.getInt(attr, IMPORTANT_FOR_ACCESSIBILITY_DEFAULT)); break; + case R.styleable.View_accessibilityDataPrivate: + setAccessibilityDataPrivate(a.getInt(attr, + ACCESSIBILITY_DATA_PRIVATE_AUTO)); + break; case R.styleable.View_accessibilityLiveRegion: setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT)); break; @@ -8518,6 +8569,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * is responsible for handling this call. * </p> * <p> + * If this view sets {@link #isAccessibilityDataPrivate()} then this view should only append + * sensitive information to an event that also sets + * {@link AccessibilityEvent#isAccessibilityDataPrivate()}. + * </p> + * <p> * <em>Note:</em> Accessibility events of certain types are not dispatched for * populating the event text via this method. For details refer to {@link AccessibilityEvent}. * </p> @@ -10419,7 +10475,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if ((mAttachInfo.mAccessibilityFetchFlags - & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0 + & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS) != 0 && Resources.resourceHasPackage(mID)) { try { String viewId = getResources().getResourceName(mID); @@ -14402,14 +14458,75 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @UnsupportedAppUsage public boolean includeForAccessibility() { if (mAttachInfo != null) { + if (isAccessibilityDataPrivate() && !AccessibilityManager.getInstance( + mContext).isRequestFromAccessibilityTool()) { + return false; + } + return (mAttachInfo.mAccessibilityFetchFlags - & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0 + & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0 || isImportantForAccessibility(); } return false; } /** + * Whether this view should restrict accessibility service access only to services that have the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + * + * <p> + * See default behavior provided by {@link #ACCESSIBILITY_DATA_PRIVATE_AUTO}. Otherwise, + * returns true for {@link #ACCESSIBILITY_DATA_PRIVATE_YES} or false for {@link + * #ACCESSIBILITY_DATA_PRIVATE_NO}. + * </p> + * + * @return True if this view should restrict accessibility service access to services that have + * the isAccessibilityTool property. + */ + @ViewDebug.ExportedProperty(category = "accessibility") + public boolean isAccessibilityDataPrivate() { + if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_YES) { + return true; + } + if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_NO) { + return false; + } + + // Views inside FLAG_SECURE windows default to accessibilityDataPrivate. + if (mAttachInfo != null && mAttachInfo.mWindowSecure) { + return true; + } + // Views that set filterTouchesWhenObscured default to accessibilityDataPrivate. + if (getFilterTouchesWhenObscured()) { + return true; + } + + // Descendants of an accessibilityDataPrivate View are also accessibilityDataPrivate. + ViewParent parent = mParent; + while (parent instanceof View) { + if (((View) parent).isAccessibilityDataPrivate()) { + return true; + } + parent = parent.getParent(); + } + + // Otherwise, default to not accessibilityDataPrivate. + return false; + } + + /** + * Specifies whether this view should only allow interactions from + * {@link android.accessibilityservice.AccessibilityService}s with the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + */ + public void setAccessibilityDataPrivate( + @AccessibilityDataPrivate int accessibilityDataPrivate) { + mAccessibilityDataPrivate = accessibilityDataPrivate; + } + + /** * Returns whether the View is considered actionable from * accessibility perspective. Such view are important for * accessibility. @@ -30096,6 +30213,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mWindowVisibility; /** + * Indicates whether the view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}. + */ + boolean mWindowSecure; + + /** * Indicates the time at which drawing started to occur. */ @UnsupportedAppUsage @@ -30272,8 +30394,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Flags related to accessibility processing. * - * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS - * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS + * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS + * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS */ int mAccessibilityFetchFlags; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b2854a3fec44..f79f0d49959d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2839,6 +2839,7 @@ public final class ViewRootImpl implements ViewParent, // However, windows are now always 32 bits by default, so choose 32 bits mAttachInfo.mUse32BitDrawingCache = true; mAttachInfo.mWindowVisibility = viewVisibility; + mAttachInfo.mWindowSecure = (lp.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0; mAttachInfo.mRecomputeGlobalAttributes = false; mLastConfigurationFromResources.setTo(config); mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 6f6936127cde..fe8d64f0152f 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -4470,10 +4470,22 @@ public interface WindowManager extends ViewManager { changes |= LAYOUT_CHANGED; } - if (!Arrays.equals(paramsForRotation, o.paramsForRotation)) { + if (paramsForRotation != o.paramsForRotation) { + if ((changes & LAYOUT_CHANGED) == 0) { + if (paramsForRotation != null && o.paramsForRotation != null + && paramsForRotation.length == o.paramsForRotation.length) { + for (int i = paramsForRotation.length - 1; i >= 0; i--) { + if (hasLayoutDiff(paramsForRotation[i], o.paramsForRotation[i])) { + changes |= LAYOUT_CHANGED; + break; + } + } + } else { + changes |= LAYOUT_CHANGED; + } + } paramsForRotation = o.paramsForRotation; checkNonRecursiveParams(); - changes |= LAYOUT_CHANGED; } if (mWallpaperTouchEventsEnabled != o.mWallpaperTouchEventsEnabled) { @@ -4484,6 +4496,21 @@ public interface WindowManager extends ViewManager { return changes; } + /** + * Returns {@code true} if the 2 params may have difference results of + * {@link WindowLayout#computeFrames}. + */ + private static boolean hasLayoutDiff(LayoutParams a, LayoutParams b) { + return a.width != b.width || a.height != b.height || a.x != b.x || a.y != b.y + || a.horizontalMargin != b.horizontalMargin + || a.verticalMargin != b.verticalMargin + || a.layoutInDisplayCutoutMode != b.layoutInDisplayCutoutMode + || a.gravity != b.gravity || !Arrays.equals(a.providedInsets, b.providedInsets) + || a.mFitInsetsTypes != b.mFitInsetsTypes + || a.mFitInsetsSides != b.mFitInsetsSides + || a.mFitInsetsIgnoringVisibility != b.mFitInsetsIgnoringVisibility; + } + @Override public String debug(String output) { output += "Contents of " + this + ":"; diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index cd0dd1df1249..2db0dcbce45e 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -46,15 +46,17 @@ import java.util.List; * </p> * <p> * The main purpose of an accessibility event is to communicate changes in the UI to an - * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect, - * if needed the user interface by examining the View hierarchy, as represented by a tree of - * {@link AccessibilityNodeInfo}s (snapshot of a View state) - * which can be used for exploring the window content. Note that the privilege for accessing - * an event's source, thus the window content, has to be explicitly requested. For more + * {@link android.accessibilityservice.AccessibilityService}. If needed, the service may then + * inspect the user interface by examining the View hierarchy through the event's + * {@link #getSource() source}, as represented by a tree of {@link AccessibilityNodeInfo}s (snapshot + * of a View state) which can be used for exploring the window content. Note that the privilege for + * accessing an event's source, thus the window content, has to be explicitly requested. For more * details refer to {@link android.accessibilityservice.AccessibilityService}. If an * accessibility service has not requested to retrieve the window content the event will - * not contain reference to its source. Also for events of type - * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available. + * not contain reference to its source. <strong>Note: </strong> for events of type + * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available, and Views that set + * {@link android.view.View#isAccessibilityDataPrivate()} may not populate all event properties on + * events sent from higher up in the view hierarchy. * </p> * <p> * This class represents various semantically different accessibility event @@ -1092,6 +1094,47 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Whether the event should only be delivered to an + * {@link android.accessibilityservice.AccessibilityService} with the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + * + * <p> + * Initial value matches the {@link android.view.View#isAccessibilityDataPrivate} property from + * the event's source node, if present, or false by default. + * </p> + * + * @return True if the event should be delivered only to isAccessibilityTool services, false + * otherwise. + * @see #setAccessibilityDataPrivate + */ + @Override + public boolean isAccessibilityDataPrivate() { + return super.isAccessibilityDataPrivate(); + } + + /** + * Sets whether the event should only be delivered to an + * {@link android.accessibilityservice.AccessibilityService} with the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + * + * <p> + * This will be set automatically based on the event's source (if present). If creating and + * sending an event directly through {@link AccessibilityManager} (where an event may have + * no source) then this method must be called explicitly if you want non-default behavior. + * </p> + * + * @param accessibilityDataPrivate True if the event should be delivered only to + * isAccessibilityTool services, false otherwise. + * @throws IllegalStateException If called from an AccessibilityService. + */ + @Override + public void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) { + super.setAccessibilityDataPrivate(accessibilityDataPrivate); + } + + /** * Gets the bit mask of the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event * * @see #SPEECH_STATE_SPEAKING_START diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 9e3195aec8a6..e89f836aaac1 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -276,6 +276,8 @@ public final class AccessibilityManager { private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor> mAudioDescriptionRequestedChangeListeners = new ArrayMap<>(); + private boolean mRequestFromAccessibilityTool; + /** * Map from a view's accessibility id to the list of request preparers set for that view */ @@ -983,6 +985,39 @@ public final class AccessibilityManager { } /** + * Whether the current accessibility request comes from an + * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool} + * property set to true. + * + * <p> + * You can use this method inside {@link AccessibilityNodeProvider} to decide how to populate + * your nodes. + * </p> + * + * <p> + * <strong>Note:</strong> The return value is valid only when an {@link AccessibilityNodeInfo} + * request is in progress, can change from one request to another, and has no meaning when a + * request is not in progress. + * </p> + * + * @return True if the current request is from a tool that sets isAccessibilityTool. + */ + public boolean isRequestFromAccessibilityTool() { + return mRequestFromAccessibilityTool; + } + + /** + * Specifies whether the current accessibility request comes from an + * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool} + * property set to true. + * + * @hide + */ + public void setRequestFromAccessibilityTool(boolean requestFromAccessibilityTool) { + mRequestFromAccessibilityTool = requestFromAccessibilityTool; + } + + /** * Registers a {@link AccessibilityRequestPreparer}. */ public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 953f2615b539..15718c4af26f 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -217,14 +217,29 @@ public class AccessibilityNodeInfo implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface PrefetchingStrategy {} - /** @hide */ - public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080; + /** + * @see AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS + * @hide + */ + public static final int FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080; - /** @hide */ - public static final int FLAG_REPORT_VIEW_IDS = 0x00000100; + /** + * @see AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS + * @hide + */ + public static final int FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS = 0x00000100; + + /** + * @see AccessibilityServiceInfo#isAccessibilityTool() + * @hide + */ + public static final int FLAG_SERVICE_IS_ACCESSIBILITY_TOOL = 0x00000200; /** @hide */ - public static final int FLAG_REPORT_MASK = 0x00000180; + public static final int FLAG_REPORT_MASK = + FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS + | FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS + | FLAG_SERVICE_IS_ACCESSIBILITY_TOOL; // Actions. diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 036316e15cb9..789c740bbba2 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -72,6 +72,7 @@ public class AccessibilityRecord { private static final int PROPERTY_FULL_SCREEN = 0x00000080; private static final int PROPERTY_SCROLLABLE = 0x00000100; private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200; + private static final int PROPERTY_ACCESSIBILITY_DATA_PRIVATE = 0x00000400; private static final int GET_SOURCE_PREFETCH_FLAGS = AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS @@ -159,6 +160,8 @@ public class AccessibilityRecord { important = root.isImportantForAccessibility(); rootViewId = root.getAccessibilityViewId(); mSourceWindowId = root.getAccessibilityWindowId(); + setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE, + root.isAccessibilityDataPrivate()); } setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important); mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId); @@ -388,6 +391,23 @@ public class AccessibilityRecord { } /** + * @see AccessibilityEvent#isAccessibilityDataPrivate + * @hide + */ + boolean isAccessibilityDataPrivate() { + return getBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE); + } + + /** + * @see AccessibilityEvent#setAccessibilityDataPrivate + * @hide + */ + void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) { + enforceNotSealed(); + setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE, accessibilityDataPrivate); + } + + /** * Gets the number of items that can be visited. * * @return The number of items. @@ -941,6 +961,8 @@ public class AccessibilityRecord { appendUnless(false, PROPERTY_CHECKED, builder); appendUnless(false, PROPERTY_FULL_SCREEN, builder); appendUnless(false, PROPERTY_SCROLLABLE, builder); + appendUnless(false, PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, builder); + appendUnless(false, PROPERTY_ACCESSIBILITY_DATA_PRIVATE, builder); append(builder, "BeforeText", mBeforeText); append(builder, "FromIndex", mFromIndex); @@ -974,6 +996,8 @@ public class AccessibilityRecord { case PROPERTY_SCROLLABLE: return "Scrollable"; case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY: return "ImportantForAccessibility"; + case PROPERTY_ACCESSIBILITY_DATA_PRIVATE: + return "AccessibilityDataPrivate"; default: return Integer.toHexString(prop); } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0b4a29899020..0c03d109f649 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -3605,7 +3605,9 @@ public final class InputMethodManager { * specifying one of those statically defined subtypes in {@code subtypes}.</p> * * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to. + * If the imiId is {@code null}, system would do nothing for this operation. * @param subtypes subtypes will be added as additional subtypes of the current input method. + * If the subtypes is {@code null}, system would do nothing for this operation. * @deprecated For IMEs that have already implemented features like customizable/downloadable * keyboard layouts/languages, please start migration to other approaches. One idea * would be exposing only one unified {@link InputMethodSubtype} then implement @@ -3618,6 +3620,11 @@ public final class InputMethodManager { mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes, UserHandle.myUserId()); } + /** + * Returns the last used {@link InputMethodSubtype} in system history. + * + * @return the last {@link InputMethodSubtype}, {@code null} if last IME have no subtype. + */ @Nullable public InputMethodSubtype getLastInputMethodSubtype() { return mServiceInvoker.getLastInputMethodSubtype(UserHandle.myUserId()); diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS index bd0a5162a036..56310aef7752 100644 --- a/core/java/android/widget/OWNERS +++ b/core/java/android/widget/OWNERS @@ -11,3 +11,5 @@ njawad@google.com per-file TextView*,EditText.java,Editor.java,EditorTouchState.java = file:../text/OWNERS per-file SpellChecker.java = file:../view/inputmethod/OWNERS + +per-file RemoteViews* = file:../appwidget/OWNERS
\ No newline at end of file diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fe0cbcb4eb7e..a0609ed6ee61 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12076,6 +12076,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { super.onPopulateAccessibilityEventInternal(event); + if (this.isAccessibilityDataPrivate() && !event.isAccessibilityDataPrivate()) { + // This view's accessibility data is private, but another view that generated this event + // is not, so don't append this view's text to the event in order to prevent sharing + // this view's contents with non-accessibility-tool services. + return; + } + final CharSequence text = getTextForAccessibility(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 212616609f35..ff7ea317d3f8 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -269,6 +269,20 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Sets whether a task should be translucent. When {@code false}, the existing translucent of + * the task applies, but when {@code true} the task will be forced to be translucent. + * @hide + */ + @NonNull + public WindowContainerTransaction setForceTranslucent( + @NonNull WindowContainerToken container, boolean forceTranslucent) { + Change chg = getOrCreateChange(container.asBinder()); + chg.mForceTranslucent = forceTranslucent; + chg.mChangeMask |= Change.CHANGE_FORCE_TRANSLUCENT; + return this; + } + + /** * Used in conjunction with a shell-transition call (usually finishTransition). This is * basically a message to the transition system that a particular task should NOT go into * PIP even though it normally would. This is to deal with some edge-case situations where @@ -854,11 +868,13 @@ public final class WindowContainerTransaction implements Parcelable { public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4; public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5; public static final int CHANGE_FORCE_NO_PIP = 1 << 6; + public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7; private final Configuration mConfiguration = new Configuration(); private boolean mFocusable = true; private boolean mHidden = false; private boolean mIgnoreOrientationRequest = false; + private boolean mForceTranslucent = false; private int mChangeMask = 0; private @ActivityInfo.Config int mConfigSetMask = 0; @@ -878,6 +894,7 @@ public final class WindowContainerTransaction implements Parcelable { mFocusable = in.readBoolean(); mHidden = in.readBoolean(); mIgnoreOrientationRequest = in.readBoolean(); + mForceTranslucent = in.readBoolean(); mChangeMask = in.readInt(); mConfigSetMask = in.readInt(); mWindowSetMask = in.readInt(); @@ -923,6 +940,9 @@ public final class WindowContainerTransaction implements Parcelable { if ((other.mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) { mIgnoreOrientationRequest = other.mIgnoreOrientationRequest; } + if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) { + mForceTranslucent = other.mForceTranslucent; + } mChangeMask |= other.mChangeMask; if (other.mActivityWindowingMode >= 0) { mActivityWindowingMode = other.mActivityWindowingMode; @@ -973,6 +993,15 @@ public final class WindowContainerTransaction implements Parcelable { return mIgnoreOrientationRequest; } + /** Gets the requested force translucent state. */ + public boolean getForceTranslucent() { + if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) == 0) { + throw new RuntimeException("Force translucent not set. " + + "Check CHANGE_FORCE_TRANSLUCENT first"); + } + return mForceTranslucent; + } + public int getChangeMask() { return mChangeMask; } @@ -1050,6 +1079,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeBoolean(mFocusable); dest.writeBoolean(mHidden); dest.writeBoolean(mIgnoreOrientationRequest); + dest.writeBoolean(mForceTranslucent); dest.writeInt(mChangeMask); dest.writeInt(mConfigSetMask); dest.writeInt(mWindowSetMask); diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto index 856bc839aee5..2b74220a1471 100644 --- a/core/proto/android/os/batteryusagestats.proto +++ b/core/proto/android/os/batteryusagestats.proto @@ -102,4 +102,21 @@ message BatteryUsageStatsAtomsProto { // Total amount of time battery was discharging during the reported session optional int64 discharge_duration_millis = 7; + + // Notes the power model used for a power component. + message PowerComponentModel { + // Holds android.os.PowerComponentEnum, or custom component value between 1000 and 9999. + optional int32 component = 1; + + enum PowerModel { + UNDEFINED = 0; + POWER_PROFILE = 1; + MEASURED_ENERGY = 2; + } + + optional PowerModel power_model = 2; + } + + // The power model used for each power component. + repeated PowerComponentModel component_models = 8; } diff --git a/core/proto/android/os/processstarttime.proto b/core/proto/android/os/processstarttime.proto deleted file mode 100644 index d0f8baee7da2..000000000000 --- a/core/proto/android/os/processstarttime.proto +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto2"; -package android.os; - -option java_multiple_files = true; - -// This message is used for statsd logging and should be kept in sync with -// frameworks/proto_logging/stats/atoms.proto -/** - * Logs information about process start time. - * - * Logged from: - * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java - */ -message ProcessStartTime { - // The uid of the ProcessRecord. - optional int32 uid = 1; - - // The process pid. - optional int32 pid = 2; - - // The process name. - // Usually package name, "system" for system server. - // Provided by ActivityManagerService. - optional string process_name = 3; - - enum StartType { - UNKNOWN = 0; - WARM = 1; - HOT = 2; - COLD = 3; - } - - // The start type. - optional StartType type = 4; - - // The elapsed realtime at the start of the process. - optional int64 process_start_time_millis = 5; - - // Number of milliseconds it takes to reach bind application. - optional int32 bind_application_delay_millis = 6; - - // Number of milliseconds it takes to finish start of the process. - optional int32 process_start_delay_millis = 7; - - // hostingType field in ProcessRecord, the component type such as "activity", - // "service", "content provider", "broadcast" or other strings. - optional string hosting_type = 8; - - // hostingNameStr field in ProcessRecord. The component class name that runs - // in this process. - optional string hosting_name = 9; - - // Broadcast action name. - optional string broadcast_action_name = 10; - - enum HostingTypeId { - HOSTING_TYPE_UNKNOWN = 0; - HOSTING_TYPE_ACTIVITY = 1; - HOSTING_TYPE_ADDED_APPLICATION = 2; - HOSTING_TYPE_BACKUP = 3; - HOSTING_TYPE_BROADCAST = 4; - HOSTING_TYPE_CONTENT_PROVIDER = 5; - HOSTING_TYPE_LINK_FAIL = 6; - HOSTING_TYPE_ON_HOLD = 7; - HOSTING_TYPE_NEXT_ACTIVITY = 8; - HOSTING_TYPE_NEXT_TOP_ACTIVITY = 9; - HOSTING_TYPE_RESTART = 10; - HOSTING_TYPE_SERVICE = 11; - HOSTING_TYPE_SYSTEM = 12; - HOSTING_TYPE_TOP_ACTIVITY = 13; - HOSTING_TYPE_EMPTY = 14; - } - - optional HostingTypeId hosting_type_id = 11; -} - diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml new file mode 100644 index 000000000000..58b8cc9438ae --- /dev/null +++ b/core/res/res/layout/side_fps_toast.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="350dp" + android:layout_gravity="center" + android:theme="?attr/alertDialogTheme"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/fp_power_button_enrollment_title" + android:singleLine="true" + android:ellipsize="end" + android:paddingLeft="20dp"/> + <Space + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1"/> + <Button + android:id="@+id/turn_off_screen" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/fp_power_button_enrollment_button_text" + android:paddingRight="20dp" + style="?android:attr/buttonBarNegativeButtonStyle" + android:maxLines="1"/> +</LinearLayout>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 08471c3429e3..d43a6c59a477 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3075,6 +3075,20 @@ <enum name="noHideDescendants" value="4" /> </attr> + <!-- Describes whether this view should allow interactions from AccessibilityServices only + if the service sets the isAccessibilityTool property. --> + <attr name="accessibilityDataPrivate" format="integer"> + <!-- The system determines whether the view's accessibility data is private + - default (recommended). --> + <enum name="auto" value="0" /> + <!-- Allow interactions from AccessibilityServices only if the service sets the + isAccessibilityTool property. --> + <enum name="yes" value="1" /> + <!-- Allow interactions from all AccessibilityServices, regardless of their + isAccessibilityTool property. --> + <enum name="no" value="2" /> + </attr> + <!-- Indicates to accessibility services whether the user should be notified when this view changes. --> <attr name="accessibilityLiveRegion" format="integer"> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d176a1d97494..215376aab11c 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3492,9 +3492,9 @@ (for side fingerprint) --> <integer name="config_sidefpsPostAuthDowntime">400</integer> - <!-- The time (in millis) that a finger tap will wait for a power button - before dismissing the power dialog during enrollment(for side fingerprint) --> - <integer name="config_sidefpsEnrollPowerPressWindow">300</integer> + <!-- The time (in millis) the clickable toast dialog will last until + automatically dismissing. This is currently used in SideFpsEventHandler --> + <integer name="config_sideFpsToastTimeout">3000</integer> <!-- This config is used to force VoiceInteractionService to start on certain low ram devices. It declares the package name of VoiceInteractionService that should be started. --> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 11c245b4c85c..ad7d03e43d29 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -114,6 +114,7 @@ <public name="handwritingBoundsOffsetTop" /> <public name="handwritingBoundsOffsetRight" /> <public name="handwritingBoundsOffsetBottom" /> + <public name="accessibilityDataPrivate" /> </staging-public-group> <staging-public-group type="id" first-id="0x01cd0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d3f2607db920..d3d049382fd0 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3561,21 +3561,17 @@ <!-- [CHAR LIMIT=NONE] Message to show in upgrading dialog when the bulk of the upgrade work is done. --> <string name="android_upgrading_complete">Finishing boot.</string> - <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button - is pressed during fingerprint enrollment. --> - <string name="fp_power_button_enrollment_title">Continue setup?</string> - <!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power button is pressed during fingerprint enrollment. --> <string name="fp_power_button_enrollment_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint.</string> - <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the - power button is pressed during fingerprint enrollment. --> - <string name="fp_power_button_enrollment_positive_button">Turn off screen</string> + <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button + is pressed during fingerprint enrollment. --> + <string name="fp_power_button_enrollment_title">Tap to turn off screen</string> - <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the + <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the power button is pressed during fingerprint enrollment. --> - <string name="fp_power_button_enrollment_negative_button">Continue setup</string> + <string name="fp_power_button_enrollment_button_text">Turn off screen</string> <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button is pressed during biometric prompt when a side fingerprint sensor is present. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e6ae7b1c8b7a..c45db7e7468f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1849,8 +1849,7 @@ <java-symbol type="string" name="fp_power_button_bp_negative_button" /> <java-symbol type="string" name="fp_power_button_enrollment_title" /> <java-symbol type="string" name="fp_power_button_enrollment_message" /> - <java-symbol type="string" name="fp_power_button_enrollment_positive_button" /> - <java-symbol type="string" name="fp_power_button_enrollment_negative_button" /> + <java-symbol type="string" name="fp_power_button_enrollment_button_text" /> <java-symbol type="string" name="global_actions" /> <java-symbol type="string" name="global_action_power_off" /> <java-symbol type="string" name="global_action_power_options" /> @@ -2627,7 +2626,11 @@ <java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/> <java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/> <java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/> - <java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/> + <java-symbol type="integer" name="config_sideFpsToastTimeout"/> + + <!-- Clickable toast used during sidefps enrollment --> + <java-symbol type="layout" name="side_fps_toast" /> + <java-symbol type="id" name="turn_off_screen" /> <!-- Face authentication messages --> <java-symbol type="string" name="face_recalibrate_notification_name" /> diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java index 2ce305440ef5..f82523c5e3a1 100644 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java +++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import android.os.AggregateBatteryConsumer; import android.os.BatteryConsumer; import android.os.BatteryUsageStats; import android.os.UidBatteryConsumer; @@ -69,10 +70,17 @@ public class BatteryUsageStatsPulledTest { assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis); assertEquals(3, proto.deviceBatteryConsumer.powerComponents.length); // Only 3 are non-empty + + final AggregateBatteryConsumer abc = bus.getAggregateBatteryConsumer( + AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); assertSameBatteryConsumer("For deviceBatteryConsumer", bus.getAggregateBatteryConsumer(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE), proto.deviceBatteryConsumer); + for (int i = 0; i < BatteryConsumer.POWER_COMPONENT_COUNT; i++) { + assertPowerComponentModel(i, abc.getPowerModel(i), proto); + } + // Now for the UidBatteryConsumers. final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers(); uidConsumers.sort((a, b) -> a.getUid() - b.getUid()); @@ -196,6 +204,34 @@ public class BatteryUsageStatsPulledTest { } } + /** + * Validates the PowerComponentModel object that matches powerComponent. + */ + private void assertPowerComponentModel(int powerComponent, + @BatteryConsumer.PowerModel int powerModel, BatteryUsageStatsAtomsProto proto) { + boolean found = false; + for (BatteryUsageStatsAtomsProto.PowerComponentModel powerComponentModel : + proto.componentModels) { + if (powerComponentModel.component == powerComponent) { + if (found) { + fail("Power component " + BatteryConsumer.powerComponentIdToString( + powerComponent) + " found multiple times in the proto"); + } + found = true; + final int expectedPowerModel = BatteryConsumer.powerModelToProtoEnum(powerModel); + assertEquals(expectedPowerModel, powerComponentModel.powerModel); + } + } + if (!found) { + final int model = BatteryConsumer.powerModelToProtoEnum(powerModel); + assertEquals( + "Power component " + BatteryConsumer.powerComponentIdToString(powerComponent) + + " was not found in the proto but has a defined power model.", + BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED, + model); + } + } + /** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */ private long convertMahToDc(double powerMah) { return (long) (powerMah * 36 + 0.5); @@ -259,11 +295,14 @@ public class BatteryUsageStatsPulledTest { builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) .setConsumedPower(30000) .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 20100) + BatteryConsumer.POWER_COMPONENT_CPU, 20100, + BatteryConsumer.POWER_MODEL_POWER_PROFILE) .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_AUDIO, 0) // Empty + BatteryConsumer.POWER_COMPONENT_AUDIO, 0, + BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CAMERA, 20150) + BatteryConsumer.POWER_COMPONENT_CAMERA, 20150, + BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) .setConsumedPowerForCustomComponent( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200) .setUsageDurationMillis( @@ -275,7 +314,8 @@ public class BatteryUsageStatsPulledTest { builder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 10100) + BatteryConsumer.POWER_COMPONENT_CPU, 10100, + BatteryConsumer.POWER_MODEL_POWER_PROFILE) .setConsumedPowerForCustomComponent( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200); @@ -285,7 +325,7 @@ public class BatteryUsageStatsPulledTest { @Test public void testLargeAtomTruncated() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[0]); + new BatteryUsageStats.Builder(new String[0], true, false); // If not truncated, this BatteryUsageStats object would generate a proto buffer // significantly larger than 50 Kb for (int i = 0; i < 3000; i++) { diff --git a/core/tests/coretests/src/android/view/WindowParamsTest.java b/core/tests/coretests/src/android/view/WindowParamsTest.java new file mode 100644 index 000000000000..49d4872922e2 --- /dev/null +++ b/core/tests/coretests/src/android/view/WindowParamsTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static org.junit.Assert.assertEquals; + +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +@Presubmit +@SmallTest +public class WindowParamsTest { + + @Test + public void testParamsForRotation() { + final WindowManager.LayoutParams paramsA = new WindowManager.LayoutParams(); + initParamsForRotation(paramsA); + final Parcel parcel = Parcel.obtain(); + paramsA.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + final WindowManager.LayoutParams paramsB = new WindowManager.LayoutParams(parcel); + assertEquals(0, paramsA.copyFrom(paramsB)); + + for (int i = 1; i <= 12; i++) { + initParamsForRotation(paramsA); + changeField(i, paramsA.paramsForRotation[0]); + assertEquals("Change not found for case " + i, + WindowManager.LayoutParams.LAYOUT_CHANGED, paramsA.copyFrom(paramsB)); + } + + parcel.recycle(); + } + + private static void initParamsForRotation(WindowManager.LayoutParams params) { + params.paramsForRotation = new WindowManager.LayoutParams[4]; + for (int i = 0; i < 4; i++) { + params.paramsForRotation[i] = new WindowManager.LayoutParams(); + } + } + + private static void changeField(int fieldCase, WindowManager.LayoutParams params) { + switch (fieldCase) { + case 1: + params.width++; + break; + case 2: + params.height++; + break; + case 3: + params.x++; + break; + case 4: + params.y++; + break; + case 5: + params.horizontalMargin++; + break; + case 6: + params.verticalMargin++; + break; + case 7: + params.layoutInDisplayCutoutMode++; + break; + case 8: + params.gravity++; + break; + case 9: + params.providedInsets = new InsetsFrameProvider[0]; + break; + case 10: + params.setFitInsetsTypes(0); + break; + case 11: + params.setFitInsetsSides(0); + break; + case 12: + params.setFitInsetsIgnoringVisibility(!params.isFitInsetsIgnoringVisibility()); + break; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt index 312af4ff7bc2..ee8c41417458 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt @@ -22,7 +22,6 @@ import android.view.View import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FlingAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat -import androidx.dynamicanimation.animation.FrameCallbackScheduler import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce @@ -125,12 +124,6 @@ class PhysicsAnimator<T> private constructor (target: T) { private var defaultFling: FlingConfig = globalDefaultFling /** - * FrameCallbackScheduler to use if it need custom FrameCallbackScheduler, if this is null, - * it will use the default FrameCallbackScheduler in the DynamicAnimation. - */ - private var customScheduler: FrameCallbackScheduler? = null - - /** * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add * just one permanent update and end listener to the DynamicAnimations. @@ -454,14 +447,6 @@ class PhysicsAnimator<T> private constructor (target: T) { this.defaultFling = defaultFling } - /** - * Set the custom FrameCallbackScheduler for all aniatmion in this animator. Set this with null for - * restoring to default FrameCallbackScheduler. - */ - fun setCustomScheduler(scheduler: FrameCallbackScheduler) { - this.customScheduler = scheduler - } - /** Starts the animations! */ fun start() { startAction() @@ -511,12 +496,9 @@ class PhysicsAnimator<T> private constructor (target: T) { // springs) on this property before flinging. cancel(animatedProperty) - // Apply the custom animation scheduler if it not null - val flingAnim = getFlingAnimation(animatedProperty, target) - flingAnim.scheduler = customScheduler ?: flingAnim.scheduler - // Apply the configuration and start the animation. - flingAnim.also { flingConfig.applyToAnimation(it) }.start() + getFlingAnimation(animatedProperty, target) + .also { flingConfig.applyToAnimation(it) }.start() } } @@ -529,18 +511,6 @@ class PhysicsAnimator<T> private constructor (target: T) { // Apply the configuration and start the animation. val springAnim = getSpringAnimation(animatedProperty, target) - // If customScheduler is exist and has not been set to the animation, - // it should set here. - if (customScheduler != null && - springAnim.scheduler != customScheduler) { - // Cancel the animation before set animation handler - if (springAnim.isRunning) { - cancel(animatedProperty) - } - // Apply the custom scheduler handler if it not null - springAnim.scheduler = customScheduler ?: springAnim.scheduler - } - // Apply the configuration and start the animation. springConfig.applyToAnimation(springAnim) animationStartActions.add(springAnim::start) @@ -596,12 +566,9 @@ class PhysicsAnimator<T> private constructor (target: T) { } } - // Apply the custom animation scheduler if it not null - val springAnim = getSpringAnimation(animatedProperty, target) - springAnim.scheduler = customScheduler ?: springAnim.scheduler - // Apply the configuration and start the spring animation. - springAnim.also { springConfig.applyToAnimation(it) }.start() + getSpringAnimation(animatedProperty, target) + .also { springConfig.applyToAnimation(it) }.start() } } }) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java index afc706ee9c8e..b8204d013105 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java @@ -19,6 +19,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import android.annotation.IntDef; @@ -55,4 +56,7 @@ public class SplitScreenConstants { {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE = {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; + + /** Flag applied to a transition change to identify it as a divider bar for animation. */ + public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 81e49f884503..b32c3eed2fb4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -29,7 +29,6 @@ import android.annotation.NonNull; import android.app.TaskInfo; import android.content.Context; import android.graphics.Rect; -import android.view.Choreographer; import android.view.Surface; import android.view.SurfaceControl; import android.window.TaskSnapshot; @@ -279,14 +278,15 @@ public class PipAnimationController { mEndValue = endValue; addListener(this); addUpdateListener(this); - mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override public void onAnimationStart(Animator animation) { mCurrentValue = mStartValue; - onStartTransaction(mLeash, newSurfaceControlTransaction()); + onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction()); if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this); } @@ -294,14 +294,16 @@ public class PipAnimationController { @Override public void onAnimationUpdate(ValueAnimator animation) { - applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(), + applySurfaceControlTransaction(mLeash, + mSurfaceControlTransactionFactory.getTransaction(), animation.getAnimatedFraction()); } @Override public void onAnimationEnd(Animator animation) { mCurrentValue = mEndValue; - final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); onEndTransaction(mLeash, tx, mTransitionDirection); if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this); @@ -348,7 +350,8 @@ public class PipAnimationController { } void setColorContentOverlay(Context context) { - final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); if (mContentOverlay != null) { mContentOverlay.detach(tx); } @@ -357,7 +360,8 @@ public class PipAnimationController { } void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { - final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); if (mContentOverlay != null) { mContentOverlay.detach(tx); } @@ -406,7 +410,7 @@ public class PipAnimationController { void setDestinationBounds(Rect destinationBounds) { mDestinationBounds.set(destinationBounds); if (mAnimationType == ANIM_TYPE_ALPHA) { - onStartTransaction(mLeash, newSurfaceControlTransaction()); + onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction()); } } @@ -441,16 +445,6 @@ public class PipAnimationController { mEndValue = endValue; } - /** - * @return {@link SurfaceControl.Transaction} instance with vsync-id. - */ - protected SurfaceControl.Transaction newSurfaceControlTransaction() { - final SurfaceControl.Transaction tx = - mSurfaceControlTransactionFactory.getTransaction(); - tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId()); - return tx; - } - @VisibleForTesting public void setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index 3ac08a66100a..b9746e338ced 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import android.view.Choreographer; import android.view.SurfaceControl; import com.android.wm.shell.R; @@ -234,4 +235,18 @@ public class PipSurfaceTransactionHelper { public interface SurfaceControlTransactionFactory { SurfaceControl.Transaction getTransaction(); } + + /** + * Implementation of {@link SurfaceControlTransactionFactory} that returns + * {@link SurfaceControl.Transaction} with VsyncId being set. + */ + public static class VsyncSurfaceControlTransactionFactory + implements SurfaceControlTransactionFactory { + @Override + public SurfaceControl.Transaction getTransaction() { + final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + return tx; + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f747b5e00759..b46eff6c55d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -304,7 +304,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper = surfaceTransactionHelper; mPipAnimationController = pipAnimationController; mPipUiEventLoggerLogger = pipUiEventLogger; - mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); mSplitScreenOptional = splitScreenOptional; mTaskOrganizer = shellTaskOrganizer; mMainExecutor = mainExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index 5a21e0734277..44d22029a5e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -33,10 +33,6 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; -import android.os.Looper; -import android.view.Choreographer; - -import androidx.dynamicanimation.animation.FrameCallbackScheduler; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; @@ -89,25 +85,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** Coordinator instance for resolving conflicts with other floating content. */ private FloatingContentCoordinator mFloatingContentCoordinator; - private ThreadLocal<FrameCallbackScheduler> mSfSchedulerThreadLocal = - ThreadLocal.withInitial(() -> { - final Looper initialLooper = Looper.myLooper(); - final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() { - @Override - public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) { - // TODO(b/222697646): remove getSfInstance usage and use vsyncId for - // transactions - Choreographer.getSfInstance().postFrameCallback(t -> runnable.run()); - } - - @Override - public boolean isCurrentThread() { - return Looper.myLooper() == initialLooper; - } - }; - return scheduler; - }); - /** * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()} * using physics animations. @@ -210,10 +187,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } public void init() { - // Note: Needs to get the shell main thread sf vsync animation handler mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); - mTemporaryBoundsPhysicsAnimator.setCustomScheduler(mSfSchedulerThreadLocal.get()); } @NonNull 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 8e1ae397ea64..4bc8e913ec4e 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 @@ -32,13 +32,13 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; -import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -147,9 +147,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private static final String TAG = StageCoordinator.class.getSimpleName(); - /** Flag applied to a transition change to identify it as a divider bar for animation. */ - public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; - private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final MainStage mMainStage; @@ -894,6 +891,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }); mShouldUpdateRecents = false; + mIsDividerRemoteAnimating = false; if (childrenToTop == null) { mSideStage.removeAllTasks(wct, false /* toTop */); @@ -1808,7 +1806,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean shouldAnimate = true; if (mSplitTransitions.isPendingEnter(transition)) { - shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); + shouldAnimate = startPendingEnterAnimation( + transition, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingRecent(transition)) { shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { @@ -1836,7 +1835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private boolean startPendingEnterAnimation(@NonNull IBinder transition, - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction finishT) { // First, verify that we actually have opened apps in both splits. TransitionInfo.Change mainChild = null; TransitionInfo.Change sideChild = null; @@ -1883,8 +1883,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " before startAnimation()."); } - finishEnterSplitScreen(t); - addDividerBarToTransition(info, t, true /* show */); + finishEnterSplitScreen(finishT); + addDividerBarToTransition(info, finishT, true /* show */); return true; } @@ -1969,7 +1969,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return false; } - addDividerBarToTransition(info, t, false /* show */); + addDividerBarToTransition(info, finishT, false /* show */); return true; } @@ -1980,23 +1980,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void addDividerBarToTransition(@NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, boolean show) { + @NonNull SurfaceControl.Transaction finishT, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); - final Rect bounds = mSplitLayout.getDividerBounds(); - barChange.setStartAbsBounds(bounds); - barChange.setEndAbsBounds(bounds); + mSplitLayout.getRefDividerBounds(mTempRect1); + barChange.setStartAbsBounds(mTempRect1); + barChange.setEndAbsBounds(mTempRect1); barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK); barChange.setFlags(FLAG_IS_DIVIDER_BAR); // Technically this should be order-0, but this is running after layer assignment // and it's a special case, so just add to end. info.addChange(barChange); - // Be default, make it visible. The remote animator can adjust alpha if it plans to animate. + if (show) { - t.setAlpha(leash, 1.f); - t.setLayer(leash, Integer.MAX_VALUE); - t.setPosition(leash, bounds.left, bounds.top); - t.show(leash); + finishT.setLayer(leash, Integer.MAX_VALUE); + finishT.setPosition(leash, mTempRect1.left, mTempRect1.top); + finishT.show(leash); + // Ensure divider surface are re-parented back into the hierarchy at the end of the + // transition. See Transition#buildFinishTransaction for more detail. + finishT.reparent(leash, mRootTaskLeash); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 5cce6b99fb11..e26c259b2397 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -20,9 +20,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; -import static com.android.wm.shell.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index a843b2a0ac39..45b69f17a861 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -162,13 +162,12 @@ class ScreenRotationAnimation { .setParent(mAnimLeash) .setBLASTLayer() .setSecure(screenshotBuffer.containsSecureLayers()) + .setOpaque(true) .setCallsite("ShellRotationAnimation") .setName("RotationLayer") .build(); t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE); - t.setPosition(mAnimLeash, 0, 0); - t.setAlpha(mAnimLeash, 1); t.show(mAnimLeash); final ColorSpace colorSpace = screenshotBuffer.getColorSpace(); @@ -181,6 +180,7 @@ class ScreenRotationAnimation { mBackColorSurface = new SurfaceControl.Builder(session) .setParent(rootLeash) .setColorLayer() + .setOpaque(true) .setCallsite("ShellRotationAnimation") .setName("BackColorSurface") .build(); @@ -189,7 +189,6 @@ class ScreenRotationAnimation { t.setLayer(mBackColorSurface, -1); t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); - t.setAlpha(mBackColorSurface, 1); t.show(mBackColorSurface); } @@ -242,7 +241,6 @@ class ScreenRotationAnimation { t.setMatrix(mScreenshotLayer, mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); - t.setAlpha(mScreenshotLayer, (float) 1.0); } /** diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 796b4c453f00..76209da47d55 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -339,6 +339,16 @@ public class GlobalSettingsValidators { VALIDATORS.put( Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put( + Global.Wearable.EARLY_UPDATES_STATUS, + new DiscreteValueValidator( + new String[] { + String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_NOT_STARTED), + String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_STARTED), + String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SUCCESS), + String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SKIPPED), + String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_ABORTED), + })); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 760f147d43c6..874e57069af3 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -656,7 +656,8 @@ public class SettingsBackupTest { Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED, Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED, Settings.Global.Wearable.BEDTIME_MODE, - Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT); + Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT, + Settings.Global.Wearable.EARLY_UPDATES_STATUS); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 154a6fca9d09..26feaf979b20 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -137,6 +137,22 @@ ] } ], + "ironwood-postsubmit": [ + { + "name": "PlatformScenarioTests", + "options": [ + { + "include-annotation": "android.platform.test.annotations.IwTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-filter": "android.platform.test.scenario.sysui" + } + ] + } + ], "auto-end-to-end-postsubmit": [ { "name": "AndroidAutomotiveHomeTests", diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java index 203b236fcdd6..7e42e1b89b1f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java @@ -170,7 +170,7 @@ public class PipSurfaceTransactionHelper { /** @return {@link SurfaceControl.Transaction} instance with vsync-id */ public static SurfaceControl.Transaction newSurfaceControlTransaction() { final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); - tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId()); + tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); return tx; } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index 9265f07ad284..33e8e3555eba 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -122,12 +122,13 @@ public class RemoteAnimationAdapterCompat { IRemoteTransitionFinishedCallback finishCallback) { final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>(); final RemoteAnimationTargetCompat[] appsCompat = - RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap); + RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); final RemoteAnimationTargetCompat[] wallpapersCompat = - RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap); - // TODO(bc-unlock): Build wrapped object for non-apps target. + RemoteAnimationTargetCompat.wrapNonApps( + info, true /* wallpapers */, t, leashMap); final RemoteAnimationTargetCompat[] nonAppsCompat = - new RemoteAnimationTargetCompat[0]; + RemoteAnimationTargetCompat.wrapNonApps( + info, false /* wallpapers */, t, leashMap); // TODO(b/177438007): Move this set-up logic into launcher's animation impl. boolean isReturnToHome = false; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index ef9e0951abf0..7c3b5fc52f0a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -16,7 +16,9 @@ package com.android.systemui.shared.system; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -24,6 +26,8 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; + import android.annotation.NonNull; import android.annotation.SuppressLint; import android.app.ActivityManager; @@ -76,7 +80,7 @@ public class RemoteAnimationTargetCompat { private final SurfaceControl mStartLeash; - // Fields used only to unrap into RemoteAnimationTarget + // Fields used only to unwrap into RemoteAnimationTarget private final Rect startBounds; public final boolean willShowImeOnTarget; @@ -203,8 +207,19 @@ public class RemoteAnimationTargetCompat { public RemoteAnimationTargetCompat(TransitionInfo.Change change, int order, TransitionInfo info, SurfaceControl.Transaction t) { - taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1; mode = newModeToLegacyMode(change.getMode()); + taskInfo = change.getTaskInfo(); + if (taskInfo != null) { + taskId = taskInfo.taskId; + isNotInRecents = !taskInfo.isRunning; + activityType = taskInfo.getActivityType(); + windowConfiguration = taskInfo.configuration.windowConfiguration; + } else { + taskId = INVALID_TASK_ID; + isNotInRecents = true; + activityType = ACTIVITY_TYPE_UNDEFINED; + windowConfiguration = new WindowConfiguration(); + } // TODO: once we can properly sync transactions across process, then get rid of this leash. leash = createLeash(info, change, order, t); @@ -221,22 +236,12 @@ public class RemoteAnimationTargetCompat { prefixOrderIndex = order; // TODO(shell-transitions): I guess we need to send content insets? evaluate how its used. contentInsets = new Rect(0, 0, 0, 0); - if (change.getTaskInfo() != null) { - isNotInRecents = !change.getTaskInfo().isRunning; - activityType = change.getTaskInfo().getActivityType(); - } else { - isNotInRecents = true; - activityType = ACTIVITY_TYPE_UNDEFINED; - } - taskInfo = change.getTaskInfo(); allowEnterPip = change.getAllowEnterPip(); mStartLeash = null; rotationChange = change.getEndRotation() - change.getStartRotation(); - windowType = INVALID_WINDOW_TYPE; + windowType = (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0 + ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE; - windowConfiguration = change.getTaskInfo() != null - ? change.getTaskInfo().configuration.windowConfiguration - : new WindowConfiguration(); startBounds = change.getStartAbsBounds(); willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0; } @@ -251,37 +256,62 @@ public class RemoteAnimationTargetCompat { } /** - * Represents a TransitionInfo object as an array of old-style targets + * Represents a TransitionInfo object as an array of old-style app targets + * + * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be + * populated by this function. If null, it is ignored. + */ + public static RemoteAnimationTargetCompat[] wrapApps(TransitionInfo info, + SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { + final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>(); + final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>(); + for (int i = 0; i < info.getChanges().size(); i++) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null) continue; + + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + // Children always come before parent since changes are in top-to-bottom z-order. + if (taskInfo != null) { + if (childTaskTargets.contains(taskInfo.taskId)) { + // has children, so not a leaf. Skip. + continue; + } + if (taskInfo.hasParentTask()) { + childTaskTargets.put(taskInfo.parentTaskId, change); + } + } + + final RemoteAnimationTargetCompat targetCompat = + new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t); + if (leashMap != null) { + leashMap.put(change.getLeash(), targetCompat.leash); + } + out.add(targetCompat); + } + + return out.toArray(new RemoteAnimationTargetCompat[out.size()]); + } + + /** + * Represents a TransitionInfo object as an array of old-style non-app targets * * @param wallpapers If true, this will return wallpaper targets; otherwise it returns * non-wallpaper targets. * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be * populated by this function. If null, it is ignored. */ - public static RemoteAnimationTargetCompat[] wrap(TransitionInfo info, boolean wallpapers, + public static RemoteAnimationTargetCompat[] wrapNonApps(TransitionInfo info, boolean wallpapers, SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>(); - final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>(); + for (int i = 0; i < info.getChanges().size(); i++) { final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null) continue; + final boolean changeIsWallpaper = (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0; if (wallpapers != changeIsWallpaper) continue; - if (!wallpapers) { - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - // Children always come before parent since changes are in top-to-bottom z-order. - if (taskInfo != null) { - if (childTaskTargets.contains(taskInfo.taskId)) { - // has children, so not a leaf. Skip. - continue; - } - if (taskInfo.hasParentTask()) { - childTaskTargets.put(taskInfo.parentTaskId, change); - } - } - } - final RemoteAnimationTargetCompat targetCompat = new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t); if (leashMap != null) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 7c1ef8c76926..f6792251d282 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -128,9 +128,10 @@ public class RemoteTransitionCompat implements Parcelable { IRemoteTransitionFinishedCallback finishedCallback) { final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>(); final RemoteAnimationTargetCompat[] apps = - RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap); + RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); final RemoteAnimationTargetCompat[] wallpapers = - RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap); + RemoteAnimationTargetCompat.wrapNonApps( + info, true /* wallpapers */, t, leashMap); // TODO(b/177438007): Move this set-up logic into launcher's animation impl. mToken = transition; // This transition is for opening recents, so recents is on-top. We want to draw diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index d701f33c4c66..c790cfe7b7b7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs import android.content.Intent +import android.content.res.Configuration import android.os.Handler import android.os.UserManager import android.provider.Settings @@ -38,9 +39,11 @@ import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED import com.android.systemui.qs.dagger.QSScope import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener +import com.android.systemui.util.LargeScreenUtils import com.android.systemui.util.ViewController import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject @@ -69,18 +72,43 @@ internal class FooterActionsController @Inject constructor( private val uiEventLogger: UiEventLogger, @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean, private val globalSetting: GlobalSettings, - private val handler: Handler + private val handler: Handler, + private val configurationController: ConfigurationController, ) : ViewController<FooterActionsView>(view) { private var globalActionsDialog: GlobalActionsDialogLite? = null private var lastExpansion = -1f private var listening: Boolean = false + private var inSplitShade = false - private val alphaAnimator = TouchAnimator.Builder() - .addFloat(mView, "alpha", 0f, 1f) - .setStartDelay(0.9f) + private val singleShadeAnimator by lazy { + // In single shade, the actions footer should only appear at the end of the expansion, + // so that it doesn't overlap with the notifications panel. + TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build() + } + + private val splitShadeAnimator by lazy { + // The Actions footer view has its own background which is the same color as the qs panel's + // background. + // We don't want it to fade in at the same time as the rest of the panel, otherwise it is + // more opaque than the rest of the panel's background. Only applies to split shade. + val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build() + val bgAlphaAnimator = + TouchAnimator.Builder() + .addFloat(mView, "backgroundAlpha", 0f, 1f) + .setStartDelay(0.9f) + .build() + // In split shade, we want the actions footer to fade in exactly at the same time as the + // rest of the shade, as there is no overlap. + TouchAnimator.Builder() + .addFloat(alphaAnimator, "position", 0f, 1f) + .addFloat(bgAlphaAnimator, "position", 0f, 1f) .build() + } + + private val animators: TouchAnimator + get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator var visible = true set(value) { @@ -95,9 +123,7 @@ internal class FooterActionsController @Inject constructor( private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view) @VisibleForTesting - internal val securityFootersSeparator = View(context).apply { - visibility = View.GONE - } + internal val securityFootersSeparator = View(context).apply { visibility = View.GONE } private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ -> val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) @@ -133,6 +159,17 @@ internal class FooterActionsController @Inject constructor( } } + private val configurationListener = + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + } + } + + private fun updateResources() { + inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources) + } + override fun onInit() { multiUserSwitchController.init() securityFooterController.init() @@ -189,6 +226,9 @@ internal class FooterActionsController @Inject constructor( securityFooterController.setOnVisibilityChangedListener(visibilityListener) fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener) + configurationController.addCallback(configurationListener) + + updateResources() updateView() } @@ -201,6 +241,7 @@ internal class FooterActionsController @Inject constructor( globalActionsDialog = null setListening(false) multiUserSetting.isListening = false + configurationController.removeCallback(configurationListener) } fun setListening(listening: Boolean) { @@ -224,7 +265,7 @@ internal class FooterActionsController @Inject constructor( } fun setExpansion(headerExpansionFraction: Float) { - alphaAnimator.setPosition(headerExpansionFraction) + animators.setPosition(headerExpansionFraction) } fun setKeyguardShowing(showing: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt index 05038b7b1b1d..309ac2a66e6b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt @@ -27,6 +27,7 @@ import android.view.MotionEvent import android.view.View import android.widget.ImageView import android.widget.LinearLayout +import androidx.annotation.Keep import com.android.settingslib.Utils import com.android.settingslib.drawable.UserIconDrawable import com.android.systemui.R @@ -45,6 +46,19 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private var qsDisabled = false private var expansionAmount = 0f + /** + * Sets the alpha of the background of this view. + * + * Used from a [TouchAnimator] in the controller. + */ + var backgroundAlpha: Float = 1f + @Keep + set(value) { + field = value + background?.alpha = (value * 255).toInt() + } + @Keep get + override fun onFinishInflate() { super.onFinishInflate() settingsContainer = findViewById(R.id.settings_button_container) @@ -117,4 +131,4 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private const val TAG = "FooterActionsView" private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE) private val MotionEvent.string - get() = "($id): ($x,$y)"
\ No newline at end of file + get() = "($id): ($x,$y)" diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 24168089f77a..7a98081fd1b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -557,9 +557,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction) { float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; - float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD + float alphaProgress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD ? mFullShadeProgress : panelExpansionFraction; - setAlphaAnimationProgress(mInSplitShade ? progress : 1); + setAlphaAnimationProgress(mInSplitShade ? alphaProgress : 1); mContainer.setExpansion(expansion); final float translationScaleY = (mInSplitShade ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1); @@ -600,7 +600,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } mQSPanelController.setIsOnKeyguard(onKeyguard); mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); - mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion); + float footerActionsExpansion = + onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion; + mQSFooterActionController.setExpansion(footerActionsExpansion); mQSPanelController.setRevealExpansion(expansion); mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 324c01959084..7155626a1aa1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -128,6 +128,8 @@ public class QSPanel extends LinearLayout implements Tunable { if (mUsingMediaPlayer) { mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext); mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); + mHorizontalLinearLayout.setVisibility( + mUsingHorizontalLayout ? View.VISIBLE : View.GONE); mHorizontalLinearLayout.setClipChildren(false); mHorizontalLinearLayout.setClipToPadding(false); @@ -445,6 +447,8 @@ public class QSPanel extends LinearLayout implements Tunable { mMediaHostView = hostView; ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this; ViewGroup currentParent = (ViewGroup) hostView.getParent(); + Log.d(getDumpableTag(), "Reattaching media host: " + horizontal + + ", current " + currentParent + ", new " + newParent); if (currentParent != newParent) { if (currentParent != null) { currentParent.removeView(hostView); @@ -589,6 +593,7 @@ public class QSPanel extends LinearLayout implements Tunable { void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force) { if (horizontal != mUsingHorizontalLayout || force) { + Log.d(getDumpableTag(), "setUsingHorizontalLayout: " + horizontal + ", " + force); mUsingHorizontalLayout = horizontal; ViewGroup newParent = horizontal ? mHorizontalContentContainer : this; switchAllContentToParent(newParent, mTileLayout); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index ec61ea6fffa8..6d5f844667e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -88,6 +88,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr public void onConfigurationChange(Configuration newConfig) { mShouldUseSplitNotificationShade = LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); + mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation, + mView.getDumpableTag()); onConfigurationChanged(); if (newConfig.orientation != mLastOrientation) { mLastOrientation = newConfig.orientation; @@ -164,6 +166,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mHost.addCallback(mQSHostCallback); setTiles(); mLastOrientation = getResources().getConfiguration().orientation; + mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag()); switchTileLayout(true); mDumpManager.registerDumpable(mView.getDumpableTag(), this); @@ -171,6 +174,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr @Override protected void onViewDetached() { + mQSLogger.logOnViewDetached(mLastOrientation, mView.getDumpableTag()); mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener); mHost.removeCallback(mQSHostCallback); @@ -325,6 +329,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr /* Whether or not the panel currently contains a media player. */ boolean horizontal = shouldUseHorizontalLayout(); if (horizontal != mUsingHorizontalLayout || force) { + mQSLogger.logSwitchTileLayout(horizontal, mUsingHorizontalLayout, force, + mView.getDumpableTag()); mUsingHorizontalLayout = horizontal; mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force); updateMediaDisappearParameters(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index ab795faf57e6..948fb1428780 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -155,6 +155,54 @@ class QSLogger @Inject constructor( }) } + fun logOnViewAttached(orientation: Int, containerName: String) { + log(DEBUG, { + str1 = containerName + int1 = orientation + }, { + "onViewAttached: $str1 orientation $int1" + }) + } + + fun logOnViewDetached(orientation: Int, containerName: String) { + log(DEBUG, { + str1 = containerName + int1 = orientation + }, { + "onViewDetached: $str1 orientation $int1" + }) + } + + fun logOnConfigurationChanged( + lastOrientation: Int, + newOrientation: Int, + containerName: String + ) { + log(DEBUG, { + str1 = containerName + int1 = lastOrientation + int2 = newOrientation + }, { + "configuration change: $str1 orientation was $int1, now $int2" + }) + } + + fun logSwitchTileLayout( + after: Boolean, + before: Boolean, + force: Boolean, + containerName: String + ) { + log(DEBUG, { + str1 = containerName + bool1 = after + bool2 = before + bool3 = force + }, { + "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" + }) + } + private fun toStateString(state: Int): String { return when (state) { Tile.STATE_ACTIVE -> "active" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 420f21db2c73..14cc6bf1ea41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -38,6 +38,7 @@ import android.os.Trace; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -126,6 +127,9 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList); private final NotifPipelineChoreographer mChoreographer; + private int mConsecutiveReentrantRebuilds = 0; + @VisibleForTesting public static final int MAX_CONSECUTIVE_REENTRANT_REBUILDS = 3; + @Inject public ShadeListBuilder( DumpManager dumpManager, @@ -310,7 +314,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { mLogger.logOnBuildList(reason); mAllEntries = entries; - mChoreographer.schedule(); + scheduleRebuild(/* reentrant = */ false); } }; @@ -1332,11 +1336,64 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { throw new RuntimeException("Missing default sectioner!"); } - private void rebuildListIfBefore(@PipelineState.StateName int state) { - mPipelineState.requireIsBefore(state); - if (mPipelineState.is(STATE_IDLE)) { + private void rebuildListIfBefore(@PipelineState.StateName int rebuildState) { + final @PipelineState.StateName int currentState = mPipelineState.getState(); + + // If the pipeline is idle, requesting an invalidation is always okay, and starts a new run. + if (currentState == STATE_IDLE) { + scheduleRebuild(/* reentrant = */ false, rebuildState); + return; + } + + // If the pipeline is running, it is okay to request an invalidation of a *later* stage. + // Since the current pipeline run hasn't run it yet, no new pipeline run is needed. + if (rebuildState > currentState) { + return; + } + + // If the pipeline is running, it is bad to request an invalidation of *earlier* stages or + // the *current* stage; this will run the pipeline more often than needed, and may even + // cause an infinite loop of pipeline runs. + // + // Unfortunately, there are some unfixed bugs that cause reentrant pipeline runs, so we keep + // a counter and allow a few reentrant runs in a row between any two non-reentrant runs. + // + // It is technically possible for a *pair* of invalidations, one reentrant and one not, to + // trigger *each other*, alternating responsibility for pipeline runs in an infinite loop + // but constantly resetting the reentrant run counter. Hopefully that doesn't happen. + scheduleRebuild(/* reentrant = */ true, rebuildState); + } + + private void scheduleRebuild(boolean reentrant) { + scheduleRebuild(reentrant, STATE_IDLE); + } + + private void scheduleRebuild(boolean reentrant, @PipelineState.StateName int rebuildState) { + if (!reentrant) { + mConsecutiveReentrantRebuilds = 0; mChoreographer.schedule(); + return; } + + final @PipelineState.StateName int currentState = mPipelineState.getState(); + + final String rebuildStateName = PipelineState.getStateName(rebuildState); + final String currentStateName = PipelineState.getStateName(currentState); + final IllegalStateException exception = new IllegalStateException( + "Reentrant notification pipeline rebuild of state " + rebuildStateName + + " while pipeline in state " + currentStateName + "."); + + mConsecutiveReentrantRebuilds++; + + if (mConsecutiveReentrantRebuilds > MAX_CONSECUTIVE_REENTRANT_REBUILDS) { + Log.e(TAG, "Crashing after more than " + MAX_CONSECUTIVE_REENTRANT_REBUILDS + + " consecutive reentrant notification pipeline rebuilds.", exception); + throw exception; + } + + Log.e(TAG, "Allowing " + mConsecutiveReentrantRebuilds + + " consecutive reentrant notification pipeline rebuild(s).", exception); + mChoreographer.schedule(); } private static int countChildren(List<ListEntry> entries) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index ef1e57b4cd3b..6e76691ae1b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -286,7 +286,7 @@ public class PreparationCoordinator implements Coordinator { if (isInflated(child)) { // TODO: May want to put an animation hint here so view manager knows to treat // this differently from a regular removal animation - freeNotifViews(child); + freeNotifViews(child, "Past last visible group child"); } } } @@ -379,7 +379,8 @@ public class PreparationCoordinator implements Coordinator { mNotifInflatingFilter.invalidateList("onInflationFinished for " + logKey(entry)); } - private void freeNotifViews(NotificationEntry entry) { + private void freeNotifViews(NotificationEntry entry, String reason) { + mLogger.logFreeNotifViews(entry, reason); mViewBarn.removeViewForEntry(entry); mNotifInflater.releaseViews(entry); // TODO: clear the entry's row here, or even better, stop setting the row on the entry! diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt index 30f13152126c..c4f4ed54e2fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -31,7 +31,7 @@ class PreparationCoordinatorLogger @Inject constructor( buffer.log(TAG, LogLevel.DEBUG, { str1 = entry.logKey }, { - "NOTIF INFLATED $str1" + "Inflation completed for notif $str1" }) } @@ -40,7 +40,16 @@ class PreparationCoordinatorLogger @Inject constructor( str1 = entry.logKey str2 = reason }, { - "NOTIF INFLATION ABORTED $str1 reason=$str2" + "Infation aborted for notif $str1 reason=$str2" + }) + } + + fun logFreeNotifViews(entry: NotificationEntry, reason: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry.logKey + str2 = reason + }, { + "Freeing content views for notif $str1 reason=$str2" }) } @@ -70,4 +79,4 @@ class PreparationCoordinatorLogger @Inject constructor( } } -private const val TAG = "PreparationCoordinator"
\ No newline at end of file +private const val TAG = "PreparationCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt index 96b9aca9c64c..276375004f76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt @@ -17,11 +17,14 @@ package com.android.systemui.statusbar.phone import android.annotation.ColorInt +import android.app.WallpaperManager import android.graphics.Color +import android.os.Handler import android.os.RemoteException import android.view.IWindowManager import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope @@ -37,6 +40,8 @@ constructor( private val windowManager: IWindowManager, @Background private val backgroundExecutor: Executor, private val dumpManager: DumpManager, + private val wallpaperManager: WallpaperManager, + @Main private val mainHandler: Handler, ) : CentralSurfacesComponent.Startable, Dumpable { @ColorInt @@ -46,9 +51,18 @@ constructor( var isLetterboxBackgroundMultiColored: Boolean = false private set + private val wallpaperColorsListener = + WallpaperManager.OnColorsChangedListener { _, _ -> + fetchBackgroundColorInfo() + } + override fun start() { dumpManager.registerDumpable(javaClass.simpleName, this) + fetchBackgroundColorInfo() + wallpaperManager.addOnColorsChangedListener(wallpaperColorsListener, mainHandler) + } + private fun fetchBackgroundColorInfo() { // Using a background executor, as binder calls to IWindowManager are blocking backgroundExecutor.execute { try { @@ -62,6 +76,7 @@ constructor( override fun stop() { dumpManager.unregisterDumpable(javaClass.simpleName) + wallpaperManager.removeOnColorsChangedListener(wallpaperColorsListener) } override fun dump(pw: PrintWriter, args: Array<out String>) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index 642e29b364d3..2ba8782c6d02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -22,13 +22,17 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.util.mockito.capture import com.android.systemui.util.settings.FakeSettings import com.android.systemui.utils.leaks.LeakCheckedTest +import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat +import javax.inject.Provider import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -42,47 +46,38 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import javax.inject.Provider import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner::class) class FooterActionsControllerTest : LeakCheckedTest() { - @Mock - private lateinit var userManager: UserManager - @Mock - private lateinit var userTracker: UserTracker - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var deviceProvisionedController: DeviceProvisionedController - @Mock - private lateinit var userInfoController: UserInfoController - @Mock - private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory - @Mock - private lateinit var multiUserSwitchController: MultiUserSwitchController - @Mock - private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite> - @Mock - private lateinit var globalActionsDialog: GlobalActionsDialogLite - @Mock - private lateinit var uiEventLogger: UiEventLogger - @Mock - private lateinit var securityFooterController: QSSecurityFooter - @Mock - private lateinit var fgsManagerController: QSFgsManagerFooter + + @get:Rule var expect: Expect = Expect.create() + + @Mock private lateinit var userManager: UserManager + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController + @Mock private lateinit var userInfoController: UserInfoController + @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory + @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController + @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite> + @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite + @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var securityFooterController: QSSecurityFooter + @Mock private lateinit var fgsManagerController: QSFgsManagerFooter @Captor private lateinit var visibilityChangedCaptor: ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener> private lateinit var controller: FooterActionsController + private val configurationController = FakeConfigurationController() private val metricsLogger: MetricsLogger = FakeMetricsLogger() - private lateinit var view: FooterActionsView private val falsingManager: FalsingManagerFake = FalsingManagerFake() + private lateinit var view: FooterActionsView private lateinit var testableLooper: TestableLooper private lateinit var fakeSettings: FakeSettings private lateinit var securityFooter: View @@ -90,12 +85,15 @@ class FooterActionsControllerTest : LeakCheckedTest() { @Before fun setUp() { + // We want to make sure testable resources are always used + context.ensureTestableResources() + MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) fakeSettings = FakeSettings() whenever(multiUserSwitchControllerFactory.create(any())) - .thenReturn(multiUserSwitchController) + .thenReturn(multiUserSwitchController) whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog) securityFooter = View(mContext) @@ -135,7 +133,7 @@ class FooterActionsControllerTest : LeakCheckedTest() { view.findViewById<View>(R.id.pm_lite).performClick() // Verify clicks are logged verify(uiEventLogger, Mockito.times(1)) - .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) + .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) } @Test @@ -299,6 +297,86 @@ class FooterActionsControllerTest : LeakCheckedTest() { assertThat(booleanCaptor.allValues.last()).isTrue() } + @Test + fun setExpansion_inSplitShade_alphaFollowsExpansion() { + enableSplitShade() + + controller.setExpansion(0f) + expect.that(view.alpha).isEqualTo(0f) + + controller.setExpansion(0.25f) + expect.that(view.alpha).isEqualTo(0.25f) + + controller.setExpansion(0.5f) + expect.that(view.alpha).isEqualTo(0.5f) + + controller.setExpansion(0.75f) + expect.that(view.alpha).isEqualTo(0.75f) + + controller.setExpansion(1f) + expect.that(view.alpha).isEqualTo(1f) + } + + @Test + fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() { + enableSplitShade() + + controller.setExpansion(0f) + expect.that(view.backgroundAlphaFraction).isEqualTo(0f) + + controller.setExpansion(0.5f) + expect.that(view.backgroundAlphaFraction).isEqualTo(0f) + + controller.setExpansion(0.9f) + expect.that(view.backgroundAlphaFraction).isEqualTo(0f) + + controller.setExpansion(0.91f) + expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f) + + controller.setExpansion(0.95f) + expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f) + + controller.setExpansion(1f) + expect.that(view.backgroundAlphaFraction).isEqualTo(1f) + } + + @Test + fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() { + disableSplitShade() + + controller.setExpansion(0f) + expect.that(view.alpha).isEqualTo(0f) + + controller.setExpansion(0.5f) + expect.that(view.alpha).isEqualTo(0f) + + controller.setExpansion(0.9f) + expect.that(view.alpha).isEqualTo(0f) + + controller.setExpansion(0.91f) + expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f) + + controller.setExpansion(0.95f) + expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f) + + controller.setExpansion(1f) + expect.that(view.alpha).isEqualTo(1f) + } + + @Test + fun setExpansion_inSingleShade_backgroundAlphaAlways1() { + disableSplitShade() + + controller.setExpansion(0f) + expect.that(view.backgroundAlphaFraction).isEqualTo(1f) + + controller.setExpansion(0.5f) + expect.that(view.backgroundAlphaFraction).isEqualTo(1f) + + controller.setExpansion(1f) + expect.that(view.backgroundAlphaFraction).isEqualTo(1f) + } + private fun setVisibilities( securityFooterVisible: Boolean, fgsFooterVisible: Boolean, @@ -311,15 +389,52 @@ class FooterActionsControllerTest : LeakCheckedTest() { } private fun inflateView(): FooterActionsView { - return LayoutInflater.from(context) - .inflate(R.layout.footer_actions, null) as FooterActionsView + return LayoutInflater.from(context).inflate(R.layout.footer_actions, null) + as FooterActionsView } private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController { - return FooterActionsController(view, multiUserSwitchControllerFactory, - activityStarter, userManager, userTracker, userInfoController, - deviceProvisionedController, securityFooterController, fgsManagerController, - falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger, - showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper)) + return FooterActionsController( + view, + multiUserSwitchControllerFactory, + activityStarter, + userManager, + userTracker, + userInfoController, + deviceProvisionedController, + securityFooterController, + fgsManagerController, + falsingManager, + metricsLogger, + globalActionsDialogProvider, + uiEventLogger, + showPMLiteButton = true, + fakeSettings, + Handler(testableLooper.looper), + configurationController) + } + + private fun enableSplitShade() { + setSplitShadeEnabled(true) + } + + private fun disableSplitShade() { + setSplitShadeEnabled(false) + } + + private fun setSplitShadeEnabled(enabled: Boolean) { + overrideResource(R.bool.config_use_split_notification_shade, enabled) + configurationController.notifyConfigurationChanged() + } +} + +private const val FLOAT_TOLERANCE = 0.01f + +private val View.backgroundAlphaFraction: Float? + get() { + return if (background != null) { + background.alpha / 255f + } else { + null + } } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 32c66d25d4aa..10f6ce8c0ec9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -205,6 +205,40 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { } @Test + public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() { + // Random test values without any meaning. They just have to be different from each other. + float expansion = 0.123f; + float panelExpansionFraction = 0.321f; + float proposedTranslation = 456f; + float squishinessFraction = 0.987f; + + QSFragment fragment = resumeAndGetFragment(); + enableSplitShade(); + + fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, + squishinessFraction); + + verify(mQSFooterActionController).setExpansion(panelExpansionFraction); + } + + @Test + public void setQsExpansion_notInSplitShade_setsFooterActionsExpansion_basedOnExpansion() { + // Random test values without any meaning. They just have to be different from each other. + float expansion = 0.123f; + float panelExpansionFraction = 0.321f; + float proposedTranslation = 456f; + float squishinessFraction = 0.987f; + + QSFragment fragment = resumeAndGetFragment(); + disableSplitShade(); + + fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, + squishinessFraction); + + verify(mQSFooterActionController).setExpansion(expansion); + } + + @Test public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() { QSFragment fragment = resumeAndGetFragment(); disableSplitShade(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java index 360eef9d214f..cf5fa87272c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java @@ -19,12 +19,14 @@ package com.android.systemui.shared.system; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_STANDARD; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CHANGING; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; @@ -64,12 +66,15 @@ public class RemoteTransitionTest extends SysuiTestCase { @Test public void testLegacyTargetExtract() { TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE) - .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER) - .addChange(TRANSIT_CLOSE, 0 /* flags */) - .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER).build(); - // Check non-wallpaper extraction - RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrap(combined, - false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */); + .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER, + createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD)) + .addChange(TRANSIT_CLOSE, 0 /* flags */, + createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD)) + .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */) + .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build(); + // Check apps extraction + RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined, + mock(SurfaceControl.Transaction.class), null /* leashes */); assertEquals(2, wrapped.length); int changeLayer = -1; int closeLayer = -1; @@ -86,17 +91,25 @@ public class RemoteTransitionTest extends SysuiTestCase { assertTrue(closeLayer < changeLayer); // Check wallpaper extraction - RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrap(combined, + RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined, true /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */); assertEquals(1, wallps.length); assertTrue(wallps[0].prefixOrderIndex < closeLayer); assertEquals(MODE_OPENING, wallps[0].mode); + + // Check non-apps extraction + RemoteAnimationTargetCompat[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined, + false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */); + assertEquals(1, nonApps.length); + assertTrue(nonApps[0].prefixOrderIndex < closeLayer); + assertEquals(MODE_CHANGING, nonApps[0].mode); } @Test public void testLegacyTargetWrapper() { TransitionInfo tinfo = new TransitionInfoBuilder(TRANSIT_CLOSE) - .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT).build(); + .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT, + createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD)).build(); final TransitionInfo.Change change = tinfo.getChanges().get(0); final Rect endBounds = new Rect(40, 60, 140, 200); change.setTaskInfo(createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_HOME)); @@ -119,11 +132,12 @@ public class RemoteTransitionTest extends SysuiTestCase { } TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, - @TransitionInfo.ChangeFlags int flags) { + @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) { final TransitionInfo.Change change = new TransitionInfo.Change(null /* token */, createMockSurface(true)); change.setMode(mode); change.setFlags(flags); + change.setTaskInfo(taskInfo); mInfo.addChange(change); return this; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index dfa38abc1ff8..9f214097ea55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.util.time.FakeSystemClock; @@ -1715,66 +1716,201 @@ public class ShadeListBuilderTest extends SysuiTestCase { assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent()); } + static class CountingInvalidator { + CountingInvalidator(Pluggable pluggableToInvalidate) { + mPluggableToInvalidate = pluggableToInvalidate; + mInvalidationCount = 0; + } + + public void setInvalidationCount(int invalidationCount) { + mInvalidationCount = invalidationCount; + } + + public void maybeInvalidate() { + if (mInvalidationCount > 0) { + mPluggableToInvalidate.invalidateList("test invalidation"); + mInvalidationCount--; + } + } + + private Pluggable mPluggableToInvalidate; + private int mInvalidationCount; + + private static final String TAG = "ShadeListBuilderTestCountingInvalidator"; + } + + @Test + public void testOutOfOrderPreGroupFilterInvalidationDoesNotThrowBeforeTooManyRuns() { + // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, + NotifFilter filter = new PackageFilter(PACKAGE_1); + CountingInvalidator invalidator = new CountingInvalidator(filter); + OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate(); + mListBuilder.addPreGroupFilter(filter); + mListBuilder.addOnBeforeTransformGroupsListener(listener); + + // WHEN we try to run the pipeline and the filter is invalidated exactly + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); + dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + + // THEN an exception is NOT thrown. + } + @Test(expected = IllegalStateException.class) - public void testOutOfOrderPreGroupFilterInvalidationThrows() { - // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage - NotifFilter filter = new PackageFilter(PACKAGE_5); - OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(null); + public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() { + // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, + NotifFilter filter = new PackageFilter(PACKAGE_1); + CountingInvalidator invalidator = new CountingInvalidator(filter); + OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate(); mListBuilder.addPreGroupFilter(filter); mListBuilder.addOnBeforeTransformGroupsListener(listener); - // WHEN we try to run the pipeline and the filter is invalidated + // WHEN we try to run the pipeline and the filter is invalidated more than + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); + dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + + // THEN an exception IS thrown. + } + + @Test + public void testNonConsecutiveOutOfOrderInvalidationDontThrowAfterTooManyRuns() { + // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, + NotifFilter filter = new PackageFilter(PACKAGE_1); + CountingInvalidator invalidator = new CountingInvalidator(filter); + OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate(); + mListBuilder.addPreGroupFilter(filter); + mListBuilder.addOnBeforeTransformGroupsListener(listener); + + // WHEN we try to run the pipeline and the filter is invalidated at least + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); + dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); + dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + + // THEN an exception is NOT thrown. + } + + @Test + public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() { + // GIVEN a NotifPromoter that gets invalidated during the sorting stage, + NotifPromoter promoter = new IdPromoter(47); + CountingInvalidator invalidator = new CountingInvalidator(promoter); + OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate(); + mListBuilder.addPromoter(promoter); + mListBuilder.addOnBeforeSortListener(listener); + + // WHEN we try to run the pipeline and the promoter is invalidated exactly + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, addNotif(0, PACKAGE_1); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is thrown + // THEN an exception is NOT thrown. } @Test(expected = IllegalStateException.class) - public void testOutOfOrderPrompterInvalidationThrows() { - // GIVEN a NotifPromoter that gets invalidated during the sorting stage + public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() { + // GIVEN a NotifPromoter that gets invalidated during the sorting stage, NotifPromoter promoter = new IdPromoter(47); - OnBeforeSortListener listener = - (list) -> promoter.invalidateList(null); + CountingInvalidator invalidator = new CountingInvalidator(promoter); + OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate(); mListBuilder.addPromoter(promoter); mListBuilder.addOnBeforeSortListener(listener); - // WHEN we try to run the pipeline and the promoter is invalidated + // WHEN we try to run the pipeline and the promoter is invalidated more than + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, addNotif(0, PACKAGE_1); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is thrown + // THEN an exception IS thrown. + } + + @Test + public void testOutOfOrderComparatorInvalidationDoesNotThrowBeforeTooManyRuns() { + // GIVEN a NotifComparator that gets invalidated during the finalizing stage, + NotifComparator comparator = new HypeComparator(PACKAGE_1); + CountingInvalidator invalidator = new CountingInvalidator(comparator); + OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate(); + mListBuilder.setComparators(singletonList(comparator)); + mListBuilder.addOnBeforeRenderListListener(listener); + + // WHEN we try to run the pipeline and the comparator is invalidated exactly + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); + dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + + // THEN an exception is NOT thrown. } @Test(expected = IllegalStateException.class) - public void testOutOfOrderComparatorInvalidationThrows() { - // GIVEN a NotifComparator that gets invalidated during the finalizing stage - NotifComparator comparator = new HypeComparator(PACKAGE_5); - OnBeforeRenderListListener listener = - (list) -> comparator.invalidateList(null); + public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() { + // GIVEN a NotifComparator that gets invalidated during the finalizing stage, + NotifComparator comparator = new HypeComparator(PACKAGE_1); + CountingInvalidator invalidator = new CountingInvalidator(comparator); + OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate(); mListBuilder.setComparators(singletonList(comparator)); mListBuilder.addOnBeforeRenderListListener(listener); - // WHEN we try to run the pipeline and the comparator is invalidated - addNotif(0, PACKAGE_1); + // WHEN we try to run the pipeline and the comparator is invalidated more than + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is thrown + // THEN an exception IS thrown. + } + + @Test + public void testOutOfOrderPreRenderFilterInvalidationDoesNotThrowBeforeTooManyRuns() { + // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage, + NotifFilter filter = new PackageFilter(PACKAGE_1); + CountingInvalidator invalidator = new CountingInvalidator(filter); + OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate(); + mListBuilder.addFinalizeFilter(filter); + mListBuilder.addOnBeforeRenderListListener(listener); + + // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); + dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + + // THEN an exception is NOT thrown. } @Test(expected = IllegalStateException.class) - public void testOutOfOrderPreRenderFilterInvalidationThrows() { - // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage - NotifFilter filter = new PackageFilter(PACKAGE_5); - OnBeforeRenderListListener listener = (list) -> filter.invalidateList(null); + public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() { + // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage, + NotifFilter filter = new PackageFilter(PACKAGE_1); + CountingInvalidator invalidator = new CountingInvalidator(filter); + OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate(); mListBuilder.addFinalizeFilter(filter); mListBuilder.addOnBeforeRenderListListener(listener); - // WHEN we try to run the pipeline and the PreRenderFilter is invalidated - addNotif(0, PACKAGE_1); + // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than + // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); dispatchBuild(); + runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is thrown + // THEN an exception IS thrown. } @Test @@ -2096,6 +2232,18 @@ public class ShadeListBuilderTest extends SysuiTestCase { mPipelineChoreographer.runIfScheduled(); } + private void runWhileScheduledUpTo(int maxRuns) { + int runs = 0; + while (mPipelineChoreographer.isScheduled()) { + if (runs > maxRuns) { + throw new IndexOutOfBoundsException( + "Pipeline scheduled itself more than " + maxRuns + "times"); + } + runs++; + mPipelineChoreographer.runIfScheduled(); + } + } + private void verifyBuiltList(ExpectedEntry ...expectedEntries) { try { assertEquals( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt index 44325dddd111..a2828d33375b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt @@ -16,19 +16,28 @@ package com.android.systemui.statusbar.phone +import android.app.WallpaperManager +import android.app.WallpaperManager.OnColorsChangedListener import android.graphics.Color +import android.os.Handler +import android.os.Looper import android.testing.AndroidTestingRunner import android.view.IWindowManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -38,17 +47,41 @@ class LetterboxBackgroundProviderTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() private val fakeExecutor = FakeExecutor(fakeSystemClock) + private val mainHandler = Handler(Looper.getMainLooper()) + + @get:Rule var expect: Expect = Expect.create() @Mock private lateinit var windowManager: IWindowManager @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var wallpaperManager: WallpaperManager private lateinit var provider: LetterboxBackgroundProvider + private var wallpaperColorsListener: OnColorsChangedListener? = null + @Before fun setUp() { MockitoAnnotations.initMocks(this) - provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager) + setUpWallpaperManager() + provider = + LetterboxBackgroundProvider( + windowManager, fakeExecutor, dumpManager, wallpaperManager, mainHandler) + } + + private fun setUpWallpaperManager() { + doAnswer { invocation -> + wallpaperColorsListener = invocation.arguments[0] as OnColorsChangedListener + return@doAnswer Unit + } + .`when`(wallpaperManager) + .addOnColorsChangedListener(any(), eq(mainHandler)) + doAnswer { + wallpaperColorsListener = null + return@doAnswer Unit + } + .`when`(wallpaperManager) + .removeOnColorsChangedListener(any(OnColorsChangedListener::class.java)) } @Test @@ -76,6 +109,31 @@ class LetterboxBackgroundProviderTest : SysuiTestCase() { } @Test + fun letterboxBackgroundColor_returnsValueFromWindowManagerOnlyOnce() { + whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED) + provider.start() + fakeExecutor.runAllReady() + expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED) + + whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN) + fakeExecutor.runAllReady() + expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED) + } + + @Test + fun letterboxBackgroundColor_afterWallpaperChanges_returnsUpdatedColor() { + whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED) + provider.start() + fakeExecutor.runAllReady() + + whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN) + wallpaperColorsListener!!.onColorsChanged(null, 0) + fakeExecutor.runAllReady() + + assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.GREEN) + } + + @Test fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() { assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false) } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index b34482f0964f..3324c526ecc2 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -354,16 +354,24 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ if (supportsFlagForNotImportantViews(info)) { if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) { - mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; + mFetchFlags |= + AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS; } else { - mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; + mFetchFlags &= + ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS; } } if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) { - mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; + mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS; } else { - mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; + mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS; + } + + if (mAccessibilityServiceInfo.isAccessibilityTool()) { + mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL; + } else { + mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL; } mRequestTouchExplorationMode = (info.flags @@ -1522,9 +1530,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return false; } + final boolean includeNotImportantViews = (mFetchFlags + & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0; if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) && !event.isImportantForAccessibility() - && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) { + && !includeNotImportantViews) { + return false; + } + + if (event.isAccessibilityDataPrivate() + && (mFetchFlags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) == 0) { return false; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6eabc981e9fe..6a6d2bb44d48 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3693,6 +3693,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT); info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; + info.setAccessibilityTool(true); final AccessibilityUserState userState; synchronized (mLock) { userState = getCurrentUserStateLocked(); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 38275f7cd348..1c571a7036ad 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -25,7 +25,6 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.app.backup.BackupManager; -import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManager; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; @@ -284,6 +283,7 @@ public class BackupManagerService extends IBackupManager.Stub { */ @Override public boolean isUserReadyForBackup(int userId) { + enforceCallingPermissionOnUserId(userId, "isUserReadyForBackup()"); return mUserServices.get(UserHandle.USER_SYSTEM) != null && mUserServices.get(userId) != null; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7f1cf0339460..0d5944e46b3c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5014,7 +5014,8 @@ public class ActivityManagerService extends IActivityManager.Stub hostingRecord.getType(), hostingRecord.getName(), shortAction, - HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType())); + HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()), + HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType())); return true; } @@ -14339,10 +14340,12 @@ public class ActivityManagerService extends IActivityManager.Stub if (oldRecord.resultTo != null) { final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent); try { + oldRecord.mIsReceiverAppRunning = true; oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo, oldRecord.intent, Activity.RESULT_CANCELED, null, null, - false, false, oldRecord.userId, oldRecord.callingUid, callingUid); + false, false, oldRecord.userId, oldRecord.callingUid, callingUid, + SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0); } catch (RemoteException e) { Slog.w(TAG, "Failure [" + queue.mQueueName + "] sending broadcast result of " diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 768fdfd4ed5c..cc2b693d36f7 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -758,6 +758,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setMaxStatsAgeMs(0) .includeProcessStateData() .includeVirtualUids() + .includePowerModels() .build(); bus = getBatteryUsageStats(List.of(querySinceReset)).get(0); break; @@ -768,6 +769,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .includeProcessStateData() .includeVirtualUids() .powerProfileModeledOnly() + .includePowerModels() .build(); bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0); break; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 6d520c3c6be8..cd5ea14bdc92 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -321,7 +321,7 @@ public final class BroadcastQueue { } private final void processCurBroadcastLocked(BroadcastRecord r, - ProcessRecord app, int receiverType, int processTemperature) throws RemoteException { + ProcessRecord app) throws RemoteException { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Process cur broadcast " + r + " for app " + app); final IApplicationThread thread = app.getThread(); @@ -367,10 +367,6 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Process cur broadcast " + r + " DELIVERED for app " + app); started = true; - FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, app.uid, - r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid, - ActivityManagerService.getShortAction(r.intent.getAction()), - receiverType, processTemperature); } finally { if (!started) { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, @@ -407,9 +403,8 @@ public final class BroadcastQueue { } try { mPendingBroadcast = null; - processCurBroadcastLocked(br, app, - BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST, - BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD); + br.mIsReceiverAppRunning = false; + processCurBroadcastLocked(br, app); didSomething = true; } catch (Exception e) { Slog.w(TAG, "Exception in new application when starting receiver " @@ -517,6 +512,22 @@ public final class BroadcastQueue { final long finishTime = SystemClock.uptimeMillis(); final long elapsed = finishTime - r.receiverTime; r.state = BroadcastRecord.IDLE; + final int curIndex = r.nextReceiver - 1; + if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) { + final Object curReceiver = r.receivers.get(curIndex); + FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid, + r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid, + ActivityManagerService.getShortAction(r.intent.getAction()), + curReceiver instanceof BroadcastFilter + ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME + : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST, + r.mIsReceiverAppRunning + ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM + : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD, + r.dispatchTime - r.enqueueTime, + r.receiverTime - r.dispatchTime, + finishTime - r.receiverTime); + } if (state == BroadcastRecord.IDLE) { Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE"); } @@ -640,7 +651,8 @@ public final class BroadcastQueue { void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser, - int receiverUid, int callingUid) throws RemoteException { + int receiverUid, int callingUid, long dispatchDelay, + long receiveDelay) throws RemoteException { // Send the intent to the receiver asynchronously using one-way binder calls. if (app != null) { final IApplicationThread thread = app.getThread(); @@ -674,12 +686,15 @@ public final class BroadcastQueue { receiver.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); } - FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, - receiverUid == -1 ? Process.SYSTEM_UID : receiverUid, - callingUid == -1 ? Process.SYSTEM_UID : callingUid, - ActivityManagerService.getShortAction(intent.getAction()), - BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME, - BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM); + if (!ordered) { + FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, + receiverUid == -1 ? Process.SYSTEM_UID : receiverUid, + callingUid == -1 ? Process.SYSTEM_UID : callingUid, + ActivityManagerService.getShortAction(intent.getAction()), + BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME, + BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM, + dispatchDelay, receiveDelay, 0 /* finish_delay */); + } } private void deliverToRegisteredReceiverLocked(BroadcastRecord r, @@ -983,7 +998,9 @@ public final class BroadcastQueue { performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId, - filter.receiverList.uid, r.callingUid); + filter.receiverList.uid, r.callingUid, + r.dispatchTime - r.enqueueTime, + r.receiverTime - r.dispatchTime); // parallel broadcasts are fire-and-forget, not bookended by a call to // finishReceiverLocked(), so we manage their activity-start token here if (filter.receiverList.app != null @@ -1166,6 +1183,7 @@ public final class BroadcastQueue { r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchRealTime = SystemClock.elapsedRealtime(); r.dispatchClockTime = System.currentTimeMillis(); + r.mIsReceiverAppRunning = true; if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, @@ -1333,10 +1351,18 @@ public final class BroadcastQueue { Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] " + r.intent.getAction() + " app=" + r.callerApp); } + if (r.dispatchTime == 0) { + // The dispatch time here could be 0, in case it's a parallel + // broadcast but it has a result receiver. Set it to now. + r.dispatchTime = now; + } + r.mIsReceiverAppRunning = true; performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false, false, r.userId, - r.callingUid, r.callingUid); + r.callingUid, r.callingUid, + r.dispatchTime - r.enqueueTime, + now - r.dispatchTime); logBootCompletedBroadcastCompletionLatencyIfPossible(r); // Set this to null so that the reference // (local and remote) isn't kept in the mBroadcastHistory. @@ -1493,6 +1519,7 @@ public final class BroadcastQueue { "Delivering ordered [" + mQueueName + "] to registered " + filter + ": " + r); + r.mIsReceiverAppRunning = true; deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx); if (r.receiver == null || !r.ordered) { // The receiver has already finished, so schedule to @@ -1856,9 +1883,8 @@ public final class BroadcastQueue { app.addPackage(info.activityInfo.packageName, info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats); maybeAddAllowBackgroundActivityStartsToken(app, r); - processCurBroadcastLocked(r, app, - BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST, - BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM); + r.mIsReceiverAppRunning = true; + processCurBroadcastLocked(r, app); return; } catch (RemoteException e) { Slog.w(TAG, "Exception when sending broadcast to " @@ -1892,7 +1918,8 @@ public final class BroadcastQueue { info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent, - r.intent.getAction()), + r.intent.getAction(), (r.alarm ? HostingRecord.TRIGGER_TYPE_ALARM + : HostingRecord.TRIGGER_TYPE_UNKNOWN)), isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY, (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false); if (r.curApp == null) { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index ae91d75ef0ce..ce4528bca887 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -135,6 +135,8 @@ final class BroadcastRecord extends Binder { ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // info about the receiver that is currently running. + boolean mIsReceiverAppRunning; // Was the receiver's app already running. + // Private refcount-management bookkeeping; start > 0 static AtomicInteger sNextToken = new AtomicInteger(1); diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java index f88a8ce83d02..efc2a2719dad 100644 --- a/services/core/java/com/android/server/am/HostingRecord.java +++ b/services/core/java/com/android/server/am/HostingRecord.java @@ -16,10 +16,27 @@ package com.android.server.am; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; -import android.os.ProcessStartTime; /** * This class describes various information required to start a process. @@ -32,6 +49,9 @@ import android.os.ProcessStartTime; * * The {@code mHostingZygote} field describes from which Zygote the new process should be spawned. * + * The {@code mTriggerType} field describes the trigger that started this processs. This could be + * an alarm or a push-message for a broadcast, for example. This is purely for logging and stats. + * * {@code mDefiningPackageName} contains the packageName of the package that defines the * component we want to start; this can be different from the packageName and uid in the * ApplicationInfo that we're creating the process with, in case the service is a @@ -71,7 +91,10 @@ public final class HostingRecord { public static final String HOSTING_TYPE_TOP_ACTIVITY = "top-activity"; public static final String HOSTING_TYPE_EMPTY = ""; - private @NonNull final String mHostingType; + public static final String TRIGGER_TYPE_UNKNOWN = "unknown"; + public static final String TRIGGER_TYPE_ALARM = "alarm"; + + @NonNull private final String mHostingType; private final String mHostingName; private final int mHostingZygote; private final String mDefiningPackageName; @@ -79,11 +102,12 @@ public final class HostingRecord { private final boolean mIsTopApp; private final String mDefiningProcessName; @Nullable private final String mAction; + @NonNull private final String mTriggerType; public HostingRecord(@NonNull String hostingType) { this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */, -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */, - null /* action */); + null /* action */, TRIGGER_TYPE_UNKNOWN); } public HostingRecord(@NonNull String hostingType, ComponentName hostingName) { @@ -91,22 +115,23 @@ public final class HostingRecord { } public HostingRecord(@NonNull String hostingType, ComponentName hostingName, - @Nullable String action) { + @Nullable String action, @Nullable String triggerType) { this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, null /* definingPackageName */, -1 /* mDefiningUid */, false /* isTopApp */, - null /* definingProcessName */, action); + null /* definingProcessName */, action, triggerType); } public HostingRecord(@NonNull String hostingType, ComponentName hostingName, String definingPackageName, int definingUid, String definingProcessName) { this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName, - definingUid, false /* isTopApp */, definingProcessName, null /* action */); + definingUid, false /* isTopApp */, definingProcessName, null /* action */, + TRIGGER_TYPE_UNKNOWN); } public HostingRecord(@NonNull String hostingType, ComponentName hostingName, boolean isTopApp) { this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */, - null /* definingProcessName */, null /* action */); + null /* definingProcessName */, null /* action */, TRIGGER_TYPE_UNKNOWN); } public HostingRecord(@NonNull String hostingType, String hostingName) { @@ -121,12 +146,12 @@ public final class HostingRecord { private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote) { this(hostingType, hostingName, hostingZygote, null /* definingPackageName */, -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */, - null /* action */); + null /* action */, TRIGGER_TYPE_UNKNOWN); } private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote, String definingPackageName, int definingUid, boolean isTopApp, - String definingProcessName, @Nullable String action) { + String definingProcessName, @Nullable String action, String triggerType) { mHostingType = hostingType; mHostingName = hostingName; mHostingZygote = hostingZygote; @@ -135,6 +160,7 @@ public final class HostingRecord { mIsTopApp = isTopApp; mDefiningProcessName = definingProcessName; mAction = action; + mTriggerType = triggerType; } public @NonNull String getType() { @@ -188,6 +214,11 @@ public final class HostingRecord { return mAction; } + /** Returns the type of trigger that led to this process start. */ + public @NonNull String getTriggerType() { + return mTriggerType; + } + /** * Creates a HostingRecord for a process that must spawn from the webview zygote * @param hostingName name of the component to be hosted in this process @@ -197,7 +228,7 @@ public final class HostingRecord { String definingPackageName, int definingUid, String definingProcessName) { return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(), WEBVIEW_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */, - definingProcessName, null /* action */); + definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN); } /** @@ -211,7 +242,7 @@ public final class HostingRecord { int definingUid, String definingProcessName) { return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(), APP_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */, - definingProcessName, null /* action */); + definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN); } /** @@ -236,35 +267,49 @@ public final class HostingRecord { public static int getHostingTypeIdStatsd(@NonNull String hostingType) { switch(hostingType) { case HOSTING_TYPE_ACTIVITY: - return ProcessStartTime.HOSTING_TYPE_ACTIVITY; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY; case HOSTING_TYPE_ADDED_APPLICATION: - return ProcessStartTime.HOSTING_TYPE_ADDED_APPLICATION; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION; case HOSTING_TYPE_BACKUP: - return ProcessStartTime.HOSTING_TYPE_BACKUP; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP; case HOSTING_TYPE_BROADCAST: - return ProcessStartTime.HOSTING_TYPE_BROADCAST; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST; case HOSTING_TYPE_CONTENT_PROVIDER: - return ProcessStartTime.HOSTING_TYPE_CONTENT_PROVIDER; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER; case HOSTING_TYPE_LINK_FAIL: - return ProcessStartTime.HOSTING_TYPE_LINK_FAIL; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL; case HOSTING_TYPE_ON_HOLD: - return ProcessStartTime.HOSTING_TYPE_ON_HOLD; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD; case HOSTING_TYPE_NEXT_ACTIVITY: - return ProcessStartTime.HOSTING_TYPE_NEXT_ACTIVITY; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY; case HOSTING_TYPE_NEXT_TOP_ACTIVITY: - return ProcessStartTime.HOSTING_TYPE_NEXT_TOP_ACTIVITY; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY; case HOSTING_TYPE_RESTART: - return ProcessStartTime.HOSTING_TYPE_RESTART; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART; case HOSTING_TYPE_SERVICE: - return ProcessStartTime.HOSTING_TYPE_SERVICE; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE; case HOSTING_TYPE_SYSTEM: - return ProcessStartTime.HOSTING_TYPE_SYSTEM; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM; case HOSTING_TYPE_TOP_ACTIVITY: - return ProcessStartTime.HOSTING_TYPE_TOP_ACTIVITY; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY; case HOSTING_TYPE_EMPTY: - return ProcessStartTime.HOSTING_TYPE_EMPTY; + return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY; + default: + return PROCESS_START_TIME__TYPE__UNKNOWN; + } + } + + /** + * Map the string triggerType to enum TriggerType defined in ProcessStartTime proto. + * @param triggerType + * @return enum TriggerType defined in ProcessStartTime proto + */ + public static int getTriggerTypeForStatsd(@NonNull String triggerType) { + switch(triggerType) { + case TRIGGER_TYPE_ALARM: + return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM; default: - return ProcessStartTime.HOSTING_TYPE_UNKNOWN; + return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN; } } } diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index d16fe1240d0c..d4ef638d0818 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -343,6 +343,9 @@ public class AttentionManagerService extends SystemService { * * Calling this multiple times for duplicate requests will be no-ops, returning true. * + * TODO(b/239130847): Maintain the proximity state in AttentionManagerService and change this + * to a polling API. + * * @return {@code true} if the framework was able to dispatch the request */ @VisibleForTesting @@ -853,9 +856,6 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") private void cancelAndUnbindLocked() { synchronized (mLock) { - if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) { - return; - } if (mCurrentAttentionCheck != null) { cancel(); } @@ -937,7 +937,7 @@ public class AttentionManagerService extends SystemService { } } - class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal { + class TestableProximityUpdateCallbackInternal implements ProximityUpdateCallbackInternal { private double mLastCallbackCode = PROXIMITY_UNKNOWN; @Override @@ -1069,6 +1069,7 @@ public class AttentionManagerService extends SystemService { private void resetStates() { synchronized (mLock) { mCurrentProximityUpdate = null; + cancelAndUnbindLocked(); } mComponentName = resolveAttentionService(mContext); } diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 26a63120d793..5620dc36c971 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -337,7 +337,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin boolean hasPublicClients = false; while (clientIterator.hasNext()) { RecMonitorClient rmc = clientIterator.next(); - if (rcdb.equals(rmc.mDispatcherCb)) { + if (rcdb.asBinder().equals(rmc.mDispatcherCb.asBinder())) { rmc.release(); clientIterator.remove(); } else { diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 74ee6800eb63..4f3fd6409cd8 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -66,6 +66,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Locale; import javax.xml.datatype.DatatypeConfigurationException; @@ -708,8 +709,8 @@ public class DisplayDeviceConfig { private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory, String suffixFormat, long idNumber) { - final String suffix = String.format(suffixFormat, idNumber); - final String filename = String.format(CONFIG_FILE_FORMAT, suffix); + final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber); + final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix); final File filePath = Environment.buildPath( baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename); final DisplayDeviceConfig config = new DisplayDeviceConfig(context); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 90e4596af420..6f3a0c516a5b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1328,9 +1328,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment(); - if (autoBrightnessAdjustmentChanged) { - mTemporaryAutoBrightnessAdjustment = Float.NaN; - } // Use the autobrightness adjustment override if set. final float autoBrightnessAdjustment; @@ -2309,14 +2306,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void handleSettingsChange(boolean userSwitch) { mPendingScreenBrightnessSetting = getScreenBrightnessSetting(); + mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); if (userSwitch) { // Don't treat user switches as user initiated change. setCurrentScreenBrightness(mPendingScreenBrightnessSetting); + updateAutoBrightnessAdjustment(); if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.resetShortTermModel(); } } - mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); // We don't bother with a pending variable for VR screen brightness since we just // immediately adapt to it. mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); @@ -2385,6 +2383,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment; mPendingAutoBrightnessAdjustment = Float.NaN; + mTemporaryAutoBrightnessAdjustment = Float.NaN; return true; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e798adf1c22a..72f3850891ad 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6423,6 +6423,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public void setVisibilityLogging(String packageName, boolean enable) { + PackageManagerServiceUtils.enforceSystemOrRootOrShell( + "Only the system or shell can set visibility logging."); final PackageStateInternal packageState = snapshot().getPackageStateInternal(packageName); if (packageState == null) { diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java index af2b902c97e8..8582f5424321 100644 --- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java +++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java @@ -23,11 +23,8 @@ import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_ import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.AlertDialog; -import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; @@ -35,12 +32,11 @@ import android.hardware.biometrics.BiometricStateListener; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; -import android.os.Build; import android.os.Handler; import android.os.PowerManager; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; +import android.view.View; +import android.view.Window; import android.view.WindowManager; import com.android.internal.R; @@ -48,49 +44,48 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; /** * Defines behavior for handling interactions between power button events and fingerprint-related * operations, for devices where the fingerprint sensor (side fps) lives on the power button. */ -public class SideFpsEventHandler { +public class SideFpsEventHandler implements View.OnClickListener { private static final int DEBOUNCE_DELAY_MILLIS = 500; - private int getTapWaitForPowerDuration(Context context) { - int tap = context.getResources().getInteger( - R.integer.config_sidefpsEnrollPowerPressWindow); - if (Build.isDebuggable()) { - tap = Settings.Secure.getIntForUser(context.getContentResolver(), - Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap, - UserHandle.USER_CURRENT); - } - return tap; - } - private static final String TAG = "SideFpsEventHandler"; - @NonNull private final Context mContext; - @NonNull private final Handler mHandler; - @NonNull private final PowerManager mPowerManager; - @NonNull private final Supplier<AlertDialog.Builder> mDialogSupplier; - @NonNull private final AtomicBoolean mSideFpsEventHandlerReady; - - @Nullable private Dialog mDialog; + @NonNull + private final Context mContext; + @NonNull + private final Handler mHandler; + @NonNull + private final PowerManager mPowerManager; + @NonNull + private final AtomicBoolean mSideFpsEventHandlerReady; + private final int mDismissDialogTimeout; + @Nullable + private SideFpsToast mDialog; private final Runnable mTurnOffDialog = () -> { dismissDialog("mTurnOffDialog"); }; - - @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener; - private @BiometricStateListener.State int mBiometricState; - private final int mTapWaitForPowerDuration; private FingerprintManager mFingerprintManager; + private DialogProvider mDialogProvider; + private long mLastPowerPressTime; - SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) { - this(context, handler, powerManager, () -> new AlertDialog.Builder(context)); + SideFpsEventHandler( + Context context, + Handler handler, + PowerManager powerManager) { + this(context, handler, powerManager, (ctx) -> { + SideFpsToast dialog = new SideFpsToast(ctx); + dialog.getWindow() + .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + }); } @VisibleForTesting @@ -98,23 +93,13 @@ public class SideFpsEventHandler { Context context, Handler handler, PowerManager powerManager, - Supplier<AlertDialog.Builder> dialogSupplier) { + DialogProvider provider) { mContext = context; mHandler = handler; mPowerManager = powerManager; - mDialogSupplier = dialogSupplier; mBiometricState = STATE_IDLE; mSideFpsEventHandlerReady = new AtomicBoolean(false); - mDialogDismissListener = - (dialog) -> { - if (mDialog == dialog) { - if (mHandler != null) { - mHandler.removeCallbacks(mTurnOffDialog); - } - mDialog = null; - } - }; - + mDialogProvider = provider; // ensure dialog is dismissed if screen goes off for unrelated reasons context.registerReceiver( new BroadcastReceiver() { @@ -127,7 +112,13 @@ public class SideFpsEventHandler { } }, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - mTapWaitForPowerDuration = getTapWaitForPowerDuration(context); + mDismissDialogTimeout = context.getResources().getInteger( + R.integer.config_sideFpsToastTimeout); + } + + @Override + public void onClick(View v) { + goToSleep(mLastPowerPressTime); } /** @@ -165,8 +156,9 @@ public class SideFpsEventHandler { Log.v(TAG, "Detected a tap to turn off dialog, ignoring"); mHandler.removeCallbacks(mTurnOffDialog); } + showDialog(eventTime, "Enroll Power Press"); + mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout); }); - showDialog(eventTime, "Enroll Power Press"); return true; case STATE_BP_AUTH: return true; @@ -176,54 +168,11 @@ public class SideFpsEventHandler { } } - @NonNull - private static Dialog showConfirmDialog( - @NonNull AlertDialog.Builder dialogBuilder, - @NonNull PowerManager powerManager, - long eventTime, - @BiometricStateListener.State int biometricState, - @NonNull DialogInterface.OnDismissListener dismissListener) { - final boolean enrolling = biometricState == STATE_ENROLLING; - final int title = - enrolling - ? R.string.fp_power_button_enrollment_title - : R.string.fp_power_button_bp_title; - final int message = - enrolling - ? R.string.fp_power_button_enrollment_message - : R.string.fp_power_button_bp_message; - final int positiveText = - enrolling - ? R.string.fp_power_button_enrollment_positive_button - : R.string.fp_power_button_bp_positive_button; - final int negativeText = - enrolling - ? R.string.fp_power_button_enrollment_negative_button - : R.string.fp_power_button_bp_negative_button; - - final Dialog confirmScreenOffDialog = - dialogBuilder - .setTitle(title) - .setMessage(message) - .setPositiveButton( - positiveText, - (dialog, which) -> { - dialog.dismiss(); - powerManager.goToSleep( - eventTime, - PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, - 0 /* flags */); - }) - .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss()) - .setOnDismissListener(dismissListener) - .setCancelable(false) - .create(); - confirmScreenOffDialog - .getWindow() - .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); - confirmScreenOffDialog.show(); - - return confirmScreenOffDialog; + private void goToSleep(long eventTime) { + mPowerManager.goToSleep( + eventTime, + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, + 0 /* flags */); } /** @@ -247,7 +196,8 @@ public class SideFpsEventHandler { if (fingerprintManager.isPowerbuttonFps()) { fingerprintManager.registerBiometricStateListener( new BiometricStateListener() { - @Nullable private Runnable mStateRunnable = null; + @Nullable + private Runnable mStateRunnable = null; @Override public void onStateChanged( @@ -281,13 +231,6 @@ public class SideFpsEventHandler { public void onBiometricAction( @BiometricStateListener.Action int action) { Log.d(TAG, "onBiometricAction " + action); - switch (action) { - case BiometricStateListener.ACTION_SENSOR_TOUCH: - mHandler.postDelayed( - mTurnOffDialog, - mTapWaitForPowerDuration); - break; - } } }); mSideFpsEventHandlerReady.set(true); @@ -309,12 +252,13 @@ public class SideFpsEventHandler { Log.d(TAG, "Ignoring show dialog"); return; } - mDialog = - showConfirmDialog( - mDialogSupplier.get(), - mPowerManager, - time, - mBiometricState, - mDialogDismissListener); + mDialog = mDialogProvider.provideDialog(mContext); + mLastPowerPressTime = time; + mDialog.show(); + mDialog.setOnClickListener(this); + } + + interface DialogProvider { + SideFpsToast provideDialog(Context context); } -} +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java new file mode 100644 index 000000000000..db074670de9b --- /dev/null +++ b/services/core/java/com/android/server/policy/SideFpsToast.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.Gravity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; + +import com.android.internal.R; + +/** + * Toast for side fps. This is typically shown during enrollment + * when a user presses the power button. + * + * This dialog is used by {@link SideFpsEventHandler} + */ +public class SideFpsToast extends Dialog { + + SideFpsToast(Context context) { + super(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.side_fps_toast); + } + + @Override + protected void onStart() { + super.onStart(); + final Window window = this.getWindow(); + WindowManager.LayoutParams windowParams = window.getAttributes(); + windowParams.dimAmount = 0; + windowParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; + windowParams.gravity = Gravity.BOTTOM; + window.setAttributes(windowParams); + } + + /** + * Sets the onClickListener for the toast dialog. + * @param listener + */ + public void setOnClickListener(View.OnClickListener listener) { + final Button turnOffScreen = findViewById(R.id.turn_off_screen); + if (turnOffScreen != null) { + turnOffScreen.setOnClickListener(listener); + } + } +} diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8de556ac4667..0332935348e9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -489,6 +489,7 @@ class Task extends TaskFragment { static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; private int mForceHiddenFlags = 0; + private boolean mForceTranslucent = false; // TODO(b/160201781): Revisit double invocation issue in Task#removeChild. /** @@ -4342,6 +4343,10 @@ class Task extends TaskFragment { return true; } + void setForceTranslucent(boolean set) { + mForceTranslucent = set; + } + @Override public boolean isAlwaysOnTop() { return !isForceHidden() && super.isAlwaysOnTop(); @@ -4360,6 +4365,11 @@ class Task extends TaskFragment { } @Override + protected boolean isForceTranslucent() { + return mForceTranslucent; + } + + @Override long getProtoFieldId() { return TASK; } @@ -5333,7 +5343,7 @@ class Task extends TaskFragment { abort = true; } if (abort) { - Slog.e(TAG, "Cannot navigateUpTo, intent =" + destIntent); + android.util.EventLog.writeEvent(0x534e4554, "238605611", callingUid, ""); foundParentInTask = false; } else { parent.deliverNewIntentLocked(callingUid, destIntent, destGrants, diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index e6afdd6c5876..c1f06e442294 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -740,6 +740,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { return false; } + protected boolean isForceTranslucent() { + return false; + } + boolean isLeafTaskFragment() { for (int i = mChildren.size() - 1; i >= 0; --i) { if (mChildren.get(i).asTaskFragment() != null) { @@ -865,7 +869,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ @VisibleForTesting boolean isTranslucent(ActivityRecord starting) { - if (!isAttached() || isForceHidden()) { + if (!isAttached() || isForceHidden() || isForceTranslucent()) { return true; } final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity, diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 95db746698d5..3b9cd368a93b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -646,6 +646,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } + if ((c.getChangeMask() + & WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) { + tr.setForceTranslucent(c.getForceTranslucent()); + effects = TRANSACT_EFFECTS_LIFECYCLE; + } + final int childWindowingMode = c.getActivityWindowingMode(); if (childWindowingMode > -1) { tr.setActivityWindowingMode(childWindowingMode); diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java index 2219d477630e..e2f56ba56f3d 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -301,6 +301,35 @@ public class BackupManagerServiceRoboTest { verify(mUserOneService, never()).initializeTransports(transports, /* observer */ null); } + /** + * Test that the backup services throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testIsUserReadyForBackup_withoutPermission_throwsSecurityException() { + BackupManagerService backupManagerService = createService(); + registerUser(backupManagerService, mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + expectThrows( + SecurityException.class, + () -> backupManagerService.isUserReadyForBackup(mUserOneId)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testIsUserReadyForBackup_withPermission_callsMethodForUser() { + BackupManagerService backupManagerService = createService(); + registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserSystemService); + registerUser(backupManagerService, mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ true); + + assertThat(backupManagerService.isUserReadyForBackup(mUserOneId)).isTrue(); + } + /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testClearBackupData_onRegisteredUser_callsMethodForUser() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java index 842b23c91e41..4a16874c7acf 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java @@ -17,13 +17,13 @@ package com.android.server.accessibility; -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS; import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; import static org.junit.Assert.assertEquals; @@ -528,7 +528,8 @@ public class AccessibilityInteractionControllerNodeRequestsTest { // different client that holds different fetch flags for TextView1. sendNodeRequestToController(nodeId, mMockClientCallback2, mMockClient2InteractionId, - FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS + FLAG_PREFETCH_SIBLINGS + | FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS | FLAG_PREFETCH_ANCESTORS); } } diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java index 7746bd6859d6..0635cc4239f4 100644 --- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.AlertDialog; import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricStateListener; import android.hardware.fingerprint.FingerprintManager; @@ -36,10 +35,13 @@ import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; +import android.testing.TestableResources; import android.view.Window; import androidx.test.InstrumentationRegistry; +import com.android.internal.R; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -47,7 +49,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; import java.util.List; @@ -68,17 +69,22 @@ public class SideFpsEventHandlerTest { BiometricStateListener.STATE_BP_AUTH, BiometricStateListener.STATE_AUTH_OTHER); + private static final Integer AUTO_DISMISS_DIALOG = 500; + @Rule public TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext(), null); - @Mock private PackageManager mPackageManager; - @Mock private FingerprintManager mFingerprintManager; - @Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext); - @Mock private AlertDialog mAlertDialog; - @Mock private Window mWindow; + @Mock + private PackageManager mPackageManager; + @Mock + private FingerprintManager mFingerprintManager; + @Mock + private SideFpsToast mDialog; + @Mock + private Window mWindow; - private TestLooper mLooper = new TestLooper(); + private final TestLooper mLooper = new TestLooper(); private SideFpsEventHandler mEventHandler; private BiometricStateListener mBiometricStateListener; @@ -88,16 +94,17 @@ public class SideFpsEventHandlerTest { mContext.addMockSystemService(PackageManager.class, mPackageManager); mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager); + TestableResources resources = mContext.getOrCreateTestableResources(); + resources.addOverride(R.integer.config_sideFpsToastTimeout, AUTO_DISMISS_DIALOG); - when(mDialogBuilder.create()).thenReturn(mAlertDialog); - when(mAlertDialog.getWindow()).thenReturn(mWindow); + when(mDialog.getWindow()).thenReturn(mWindow); mEventHandler = new SideFpsEventHandler( mContext, new Handler(mLooper.getLooper()), mContext.getSystemService(PowerManager.class), - () -> mDialogBuilder); + (ctx) -> mDialog); } @Test @@ -108,7 +115,7 @@ public class SideFpsEventHandlerTest { assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse(); mLooper.dispatchAll(); - verify(mAlertDialog, never()).show(); + verify(mDialog, never()).show(); } @Test @@ -120,7 +127,7 @@ public class SideFpsEventHandlerTest { assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse(); mLooper.dispatchAll(); - verify(mAlertDialog, never()).show(); + verify(mDialog, never()).show(); } } @@ -133,7 +140,7 @@ public class SideFpsEventHandlerTest { assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse(); mLooper.dispatchAll(); - verify(mAlertDialog, never()).show(); + verify(mDialog, never()).show(); } } @@ -148,7 +155,7 @@ public class SideFpsEventHandlerTest { assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse(); mLooper.dispatchAll(); - verify(mAlertDialog, never()).show(); + verify(mDialog, never()).show(); } @Test @@ -159,18 +166,18 @@ public class SideFpsEventHandlerTest { assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse(); mLooper.dispatchAll(); - verify(mAlertDialog, never()).show(); + verify(mDialog, never()).show(); } @Test - public void promptsWhenBPisActive() throws Exception { + public void doesNotpromptWhenBPisActive() throws Exception { setupWithSensor(true /* hasSideFps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_BP_AUTH); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); - verify(mAlertDialog, never()).show(); + verify(mDialog, never()).show(); } @Test @@ -181,57 +188,40 @@ public class SideFpsEventHandlerTest { assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); - verify(mAlertDialog).show(); - verify(mAlertDialog, never()).dismiss(); + verify(mDialog).show(); } @Test - public void dismissesDialogOnTouchWhenEnrolling() throws Exception { + public void dialogDismissesAfterTime() throws Exception { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_ENROLLING); - when(mAlertDialog.isShowing()).thenReturn(true); + when(mDialog.isShowing()).thenReturn(true); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); - verify(mAlertDialog).show(); - - mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); - mLooper.moveTimeForward(10000); + verify(mDialog).show(); + mLooper.moveTimeForward(AUTO_DISMISS_DIALOG); mLooper.dispatchAll(); - - verify(mAlertDialog).dismiss(); + verify(mDialog).dismiss(); } @Test - public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception { + public void dialogDoesNotDismissOnSensorTouch() throws Exception { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_ENROLLING); - when(mAlertDialog.isShowing()).thenReturn(true); + when(mDialog.isShowing()).thenReturn(true); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); - verify(mAlertDialog).show(); + verify(mDialog).show(); mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); - assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue(); - + mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1); mLooper.dispatchAll(); - verify(mAlertDialog, never()).dismiss(); - } - @Test - public void showDialogAfterTap() throws Exception { - setupWithSensor(true /* hasSfps */, true /* initialized */); - - setBiometricState(BiometricStateListener.STATE_ENROLLING); - when(mAlertDialog.isShowing()).thenReturn(true); - mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); - assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue(); - - mLooper.dispatchAll(); - verify(mAlertDialog).show(); + verify(mDialog, never()).dismiss(); } private void setBiometricState(@BiometricStateListener.State int newState) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index 69ba8ad58c7c..47ff3d584907 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -79,6 +79,8 @@ public class ContentRecorderTests extends WindowTestsBase { private ConfigListener mConfigListener; private CountDownLatch mLatch; + private VirtualDisplay mVirtualDisplay; + @Before public void setUp() { // GIVEN SurfaceControl can successfully mirror the provided surface. sSurfaceSize = new Point( @@ -89,10 +91,10 @@ public class ContentRecorderTests extends WindowTestsBase { doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt()); // GIVEN the VirtualDisplay associated with the session (so the display has state ON). - VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", + mVirtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", sSurfaceSize.x, sSurfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR); - final int displayId = virtualDisplay.getDisplay().getDisplayId(); + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); mWm.mRoot.onDisplayAdded(displayId); final DisplayContent virtualDisplayContent = mWm.mRoot.getDisplayContent(displayId); mContentRecorder = new ContentRecorder(virtualDisplayContent); @@ -119,6 +121,8 @@ public class ContentRecorderTests extends WindowTestsBase { @After public void teardown() { DeviceConfig.removeOnPropertiesChangedListener(mConfigListener); + mVirtualDisplay.release(); + mWm.mRoot.onDisplayRemoved(mVirtualDisplay.getDisplay().getDisplayId()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 969353216d20..a62625cea46c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -578,6 +578,22 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testContainerTranslucentChanges() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build(); + WindowContainerTransaction t = new WindowContainerTransaction(); + assertFalse(rootTask.isTranslucent(activity)); + t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), true); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); + assertTrue(rootTask.isTranslucent(activity)); + t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), false); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); + assertFalse(rootTask.isTranslucent(activity)); + } + + @Test public void testSetIgnoreOrientationRequest_taskDisplayArea() { removeGlobalMinSizeRestriction(); final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 85b9f759beac..9562c1a5bcf9 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -508,6 +508,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // current USB state private boolean mHostConnected; private boolean mUsbAccessoryConnected; + private boolean mInHostModeWithNoAccessoryConnected; private boolean mSourcePower; private boolean mSinkPower; private boolean mConfigured; @@ -959,6 +960,17 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mSupportsAllCombinations = false; } + if (mHostConnected) { + if (!mUsbAccessoryConnected) { + mInHostModeWithNoAccessoryConnected = true; + } else { + mInHostModeWithNoAccessoryConnected = false; + } + } else { + // if not in host mode, reset value to false + mInHostModeWithNoAccessoryConnected = false; + } + mAudioAccessorySupported = port.isModeSupported(MODE_AUDIO_ACCESSORY); args.recycle(); @@ -983,6 +995,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser Slog.i(TAG, "HOST_STATE connected:" + mUsbAccessoryConnected); } + if (!devices.hasNext()) { + mInHostModeWithNoAccessoryConnected = true; + } else { + mInHostModeWithNoAccessoryConnected = false; + } + mHideUsbNotification = false; while (devices.hasNext()) { Map.Entry pair = (Map.Entry) devices.next(); @@ -1192,7 +1210,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // Dont show the notification when connected to a USB peripheral // and the link does not support PR_SWAP and DR_SWAP - if (mHideUsbNotification && !mSupportsAllCombinations) { + if ((mHideUsbNotification || mInHostModeWithNoAccessoryConnected) + && !mSupportsAllCombinations) { if (mUsbNotificationId != 0) { mNotificationManager.cancelAsUser(null, mUsbNotificationId, UserHandle.ALL); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 1e0e8366c385..2eae68bd3182 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -18,6 +18,8 @@ package com.android.server.voiceinteraction; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.RECORD_AUDIO; +import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; +import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS; @@ -58,6 +60,7 @@ import static com.android.server.voiceinteraction.SoundTriggerSessionPermissions import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.attention.AttentionManagerInternal; import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; @@ -186,6 +189,12 @@ final class HotwordDetectionConnection { final int mUser; final Context mContext; + final AttentionManagerInternal mAttentionManagerInternal; + + final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal = + this::setProximityMeters; + + volatile HotwordDetectionServiceIdentity mIdentity; private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback; private Instant mLastRestartInstant; @@ -206,6 +215,8 @@ final class HotwordDetectionConnection { private @NonNull ServiceConnection mRemoteHotwordDetectionService; private IBinder mAudioFlinger; private boolean mDebugHotwordLogging = false; + @GuardedBy("mLock") + private double mProximityMeters = PROXIMITY_UNKNOWN; HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, ComponentName serviceName, int userId, @@ -233,6 +244,8 @@ final class HotwordDetectionConnection { mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed); mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked(); + mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class); + mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal); mLastRestartInstant = Instant.now(); updateStateAfterProcessStart(options, sharedMemory); @@ -397,6 +410,7 @@ final class HotwordDetectionConnection { if (mAudioFlinger != null) { mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0); } + mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal); } void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) { @@ -464,6 +478,7 @@ final class HotwordDetectionConnection { mSoftwareCallback.onError(); return; } + saveProximityMetersToBundle(result); mSoftwareCallback.onDetected(result, null, null); if (result != null) { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) @@ -591,6 +606,7 @@ final class HotwordDetectionConnection { externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION); return; } + saveProximityMetersToBundle(result); externalCallback.onKeyphraseDetected(recognitionEvent, result); if (result != null) { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) @@ -1159,6 +1175,20 @@ final class HotwordDetectionConnection { }); } + private void saveProximityMetersToBundle(HotwordDetectedResult result) { + synchronized (mLock) { + if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) { + result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters); + } + } + } + + private void setProximityMeters(double proximityMeters) { + synchronized (mLock) { + mProximityMeters = proximityMeters; + } + } + private static void bestEffortClose(Closeable... closeables) { for (Closeable closeable : closeables) { bestEffortClose(closeable); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 1f97d862fb6b..f28408e1abc9 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -194,13 +194,24 @@ public class SubscriptionManager { } @Override - public T recompute(Void aVoid) { + public T recompute(Void query) { + // This always throws on any error. The exceptions must be handled outside + // the cache. + try { + return mInterfaceMethod.applyOrThrow(TelephonyManager.getSubscriptionService()); + } catch (Exception re) { + throw new RuntimeException(re); + } + } + + @Override + public T query(Void query) { T result = mDefaultValue; try { ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { - result = mInterfaceMethod.applyOrThrow(iSub); + result = super.query(query); } } catch (Exception ex) { Rlog.w(LOG_TAG, "Failed to recompute cache key for " + mCacheKeyProperty); @@ -229,12 +240,24 @@ public class SubscriptionManager { @Override public T recompute(Integer query) { + // This always throws on any error. The exceptions must be handled outside + // the cache. + try { + return mInterfaceMethod.applyOrThrow( + TelephonyManager.getSubscriptionService(), query); + } catch (Exception re) { + throw new RuntimeException(re); + } + } + + @Override + public T query(Integer query) { T result = mDefaultValue; try { ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { - result = mInterfaceMethod.applyOrThrow(iSub, query); + result = super.query(query); } } catch (Exception ex) { Rlog.w(LOG_TAG, "Failed to recompute cache key for " + mCacheKeyProperty); @@ -4118,4 +4141,3 @@ public class SubscriptionManager { usageSetting, subscriptionId, mContext.getOpPackageName())); } } - diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index cd07b1ebbf92..46a846bce35f 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -641,7 +641,23 @@ class PackageFlattener { local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); } else { // resource isn't exempt from collapse, add it as obfuscated value - local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); + if (entry.overlayable_item) { + // if the resource name of the specific entry is obfuscated and this + // entry is in the overlayable list, the overlay can't work on this + // overlayable at runtime because the name has been obfuscated in + // resources.arsc during flatten operation. + const OverlayableItem& item = entry.overlayable_item.value(); + context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source) + << "The resource name of overlayable entry " + << resource_name.to_string() << "'" + << " shouldn't be obfuscated in resources.arsc"); + + local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); + } else { + // TODO(b/228192695): output the entry.name and Resource id to make + // de-obfuscated possible. + local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); + } } // Group values by configuration. for (auto& config_value : entry.values) { diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index 1dd2468b5868..e48fca61fef8 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -878,4 +878,52 @@ TEST_F(TableFlattenerTest, FlattenCustomResourceTypes) { Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); } +TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseNotInExemption) { + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= PolicyFlags::PUBLIC; + + std::string name = "com.app.test:color/overlayable_color"; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8), + 0xffaabbcc)) + .SetOverlayable(name, overlayable_item) + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + + ResTable res_table; + EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue()); + EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {}, + Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u), + testing::IsTrue()); +} + +TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseInExemption) { + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= PolicyFlags::PUBLIC; + + std::string name = "com.app.test:color/overlayable_color"; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8), + 0xffaabbcc)) + .SetOverlayable(name, overlayable_item) + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + options.name_collapse_exemptions.insert( + ResourceName({}, ResourceType::kColor, "overlayable_color")); + + ResTable res_table; + EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue()); + EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {}, + Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u), + testing::IsTrue()); +} + } // namespace aapt |