diff options
224 files changed, 8335 insertions, 2463 deletions
diff --git a/Android.bp b/Android.bp index 54983d615913..3b8ef617630b 100644 --- a/Android.bp +++ b/Android.bp @@ -156,7 +156,6 @@ java_library { "framework-scheduling.stubs.module_lib", "framework-sdkextensions.stubs.module_lib", "framework-statsd.stubs.module_lib", - "framework-supplementalapi.stubs.module_lib", "framework-supplementalprocess.stubs.module_lib", "framework-tethering.stubs.module_lib", "framework-uwb.stubs.module_lib", @@ -181,7 +180,6 @@ java_library { "framework-scheduling.impl", "framework-sdkextensions.impl", "framework-statsd.impl", - "framework-supplementalapi.impl", "framework-supplementalprocess.impl", "framework-tethering.impl", "framework-uwb.impl", diff --git a/StubLibraries.bp b/StubLibraries.bp index 92c63c9ab841..3b11036495c8 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -250,7 +250,6 @@ modules_public_stubs = [ "framework-scheduling.stubs", "framework-sdkextensions.stubs", "framework-statsd.stubs", - "framework-supplementalapi.stubs", "framework-supplementalprocess.stubs", "framework-tethering.stubs", "framework-uwb.stubs", @@ -273,7 +272,6 @@ modules_system_stubs = [ "framework-scheduling.stubs.system", "framework-sdkextensions.stubs.system", "framework-statsd.stubs.system", - "framework-supplementalapi.stubs", "framework-supplementalprocess.stubs", "framework-tethering.stubs.system", "framework-uwb.stubs.system", diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java index 005c447c7220..a6a007f46b58 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -46,6 +46,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; +import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -681,8 +682,8 @@ class Agent { final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName); final double perc = batteryLevel / 100d; // TODO: maybe don't give credits to bankrupt apps until battery level >= 50% - if (ledger.getCurrentBalance() < minBalance) { - final long shortfall = minBalance - getBalanceLocked(userId, pkgName); + final long shortfall = minBalance - ledger.getCurrentBalance(); + if (shortfall > 0) { recordTransactionLocked(userId, pkgName, ledger, new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME, null, (long) (perc * shortfall)), true); @@ -1170,5 +1171,57 @@ class Agent { void dumpLocked(IndentingPrintWriter pw) { pw.println(); mBalanceThresholdAlarmQueue.dump(pw); + + pw.println(); + pw.println("Ongoing events:"); + pw.increaseIndent(); + boolean printedEvents = false; + final long nowElapsed = SystemClock.elapsedRealtime(); + for (int u = mCurrentOngoingEvents.numMaps() - 1; u >= 0; --u) { + final int userId = mCurrentOngoingEvents.keyAt(u); + for (int p = mCurrentOngoingEvents.numElementsForKey(userId) - 1; p >= 0; --p) { + final String pkgName = mCurrentOngoingEvents.keyAt(u, p); + final SparseArrayMap<String, OngoingEvent> ongoingEvents = + mCurrentOngoingEvents.get(userId, pkgName); + + boolean printedApp = false; + + for (int e = ongoingEvents.numMaps() - 1; e >= 0; --e) { + final int eventId = ongoingEvents.keyAt(e); + for (int t = ongoingEvents.numElementsForKey(eventId) - 1; t >= 0; --t) { + if (!printedApp) { + printedApp = true; + pw.println(appToString(userId, pkgName)); + pw.increaseIndent(); + } + printedEvents = true; + + OngoingEvent ongoingEvent = ongoingEvents.valueAt(e, t); + + pw.print(EconomicPolicy.eventToString(ongoingEvent.eventId)); + if (ongoingEvent.tag != null) { + pw.print("("); + pw.print(ongoingEvent.tag); + pw.print(")"); + } + pw.print(" runtime="); + TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw); + pw.print(" delta/sec="); + pw.print(ongoingEvent.deltaPerSec); + pw.print(" refCount="); + pw.print(ongoingEvent.refCount); + pw.println(); + } + } + + if (printedApp) { + pw.decreaseIndent(); + } + } + } + if (!printedEvents) { + pw.print("N/A"); + } + pw.decreaseIndent(); } } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index 20a300abe72a..36895a5c3a25 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -567,20 +567,23 @@ public class InternalResourceService extends SystemService { final String pkgName = event.getPackageName(); if (DEBUG) { Slog.d(TAG, "Processing event " + event.getEventType() + + " (" + event.mInstanceId + ")" + " for " + appToString(userId, pkgName)); } final long nowElapsed = SystemClock.elapsedRealtime(); switch (event.getEventType()) { case UsageEvents.Event.ACTIVITY_RESUMED: mAgent.noteOngoingEventLocked(userId, pkgName, - EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed); + EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId), + nowElapsed); break; case UsageEvents.Event.ACTIVITY_PAUSED: case UsageEvents.Event.ACTIVITY_STOPPED: case UsageEvents.Event.ACTIVITY_DESTROYED: final long now = getCurrentTimeMillis(); mAgent.stopOngoingActionLocked(userId, pkgName, - EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed, now); + EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId), + nowElapsed, now); break; case UsageEvents.Event.USER_INTERACTION: case UsageEvents.Event.CHOOSER_ACTION: diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java index a234ae6142fc..f4917ad82761 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java @@ -138,6 +138,11 @@ class Ledger { dumpTime(pw, transaction.endTimeMs); pw.print(": "); pw.print(EconomicPolicy.eventToString(transaction.eventId)); + if (transaction.tag != null) { + pw.print("("); + pw.print(transaction.tag); + pw.print(")"); + } pw.print(" --> "); pw.println(narcToString(transaction.delta)); } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index af4053fb6c8c..05a0661914dc 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -105,6 +105,7 @@ static const int TEXT_MISSING_VALUE = INT_MIN; static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress"; static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays"; +static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled"; static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1; static constexpr size_t TEXT_POS_LEN_MAX = 16; static const int DYNAMIC_COLOR_COUNT = 4; @@ -1320,6 +1321,8 @@ bool BootAnimation::movie() { } if (!anyPartHasClock) { mClockEnabled = false; + } else if (!android::base::GetBoolProperty(CLOCK_ENABLED_PROP_NAME, false)) { + mClockEnabled = false; } // Check if npot textures are supported diff --git a/core/api/current.txt b/core/api/current.txt index 7f46577505ad..ee44198dcc26 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10127,8 +10127,10 @@ package android.companion { ctor public CompanionDeviceService(); method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessage(int, int, @NonNull byte[]); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); - method @MainThread public abstract void onDeviceAppeared(@NonNull String); - method @MainThread public abstract void onDeviceDisappeared(@NonNull String); + method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String); + method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo); + method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String); + method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo); method @MainThread public void onDispatchMessage(int, int, @NonNull byte[]); field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService"; } @@ -17886,6 +17888,7 @@ package android.hardware { field public static final int RGB_565 = 4; // 0x4 field public static final int RGB_888 = 3; // 0x3 field public static final int S_UI8 = 53; // 0x35 + field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L field public static final long USAGE_CPU_READ_OFTEN = 3L; // 0x3L field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L @@ -49044,6 +49047,7 @@ package android.view { method @NonNull public android.view.SurfaceControl build(); method @NonNull public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int); method @NonNull public android.view.SurfaceControl.Builder setFormat(int); + method @NonNull public android.view.SurfaceControl.Builder setHidden(boolean); method @NonNull public android.view.SurfaceControl.Builder setName(@NonNull String); method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean); method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl); @@ -49058,11 +49062,18 @@ package android.view { method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction); method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl); method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float); + method @NonNull public android.view.SurfaceControl.Transaction setBuffer(@NonNull android.view.SurfaceControl, @Nullable android.hardware.HardwareBuffer); method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public android.view.SurfaceControl.Transaction setBufferTransform(@NonNull android.view.SurfaceControl, int); + method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect); + method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region); method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int); method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int); method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int); method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean); + method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float); + method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float); method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR; diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index b8ce02e72571..77569063d87a 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -215,6 +215,10 @@ package android.media.session { package android.net { + public final class ConnectivityFrameworkInitializerTiramisu { + method public static void registerServiceWrappers(); + } + public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { ctor public EthernetNetworkSpecifier(@NonNull String); method public int describeContents(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 654be5bed8c8..5a6ea8962d2f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -70,6 +70,7 @@ package android { field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE"; field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT"; field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; + field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP"; field public static final String BRICK = "android.permission.BRICK"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"; @@ -2428,6 +2429,8 @@ package android.companion { method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations(); method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceAppeared(int); + method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceDisappeared(int); method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener); } @@ -3897,6 +3900,127 @@ package android.hardware.hdmi { } +package android.hardware.input { + + public final class VirtualKeyEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getAction(); + method public int getKeyCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int ACTION_DOWN = 0; // 0x0 + field public static final int ACTION_UP = 1; // 0x1 + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualKeyEvent> CREATOR; + } + + public static final class VirtualKeyEvent.Builder { + ctor public VirtualKeyEvent.Builder(); + method @NonNull public android.hardware.input.VirtualKeyEvent build(); + method @NonNull public android.hardware.input.VirtualKeyEvent.Builder setAction(int); + method @NonNull public android.hardware.input.VirtualKeyEvent.Builder setKeyCode(int); + } + + public class VirtualKeyboard implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent); + } + + public class VirtualMouse implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualMouseButtonEvent); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull android.hardware.input.VirtualMouseRelativeEvent); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull android.hardware.input.VirtualMouseScrollEvent); + } + + public final class VirtualMouseButtonEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getAction(); + method public int getButtonCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int ACTION_BUTTON_PRESS = 11; // 0xb + field public static final int ACTION_BUTTON_RELEASE = 12; // 0xc + field public static final int BUTTON_BACK = 8; // 0x8 + field public static final int BUTTON_FORWARD = 16; // 0x10 + field public static final int BUTTON_PRIMARY = 1; // 0x1 + field public static final int BUTTON_SECONDARY = 2; // 0x2 + field public static final int BUTTON_TERTIARY = 4; // 0x4 + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseButtonEvent> CREATOR; + } + + public static final class VirtualMouseButtonEvent.Builder { + ctor public VirtualMouseButtonEvent.Builder(); + method @NonNull public android.hardware.input.VirtualMouseButtonEvent build(); + method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setAction(int); + method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setButtonCode(int); + } + + public final class VirtualMouseRelativeEvent implements android.os.Parcelable { + method public int describeContents(); + method public float getRelativeX(); + method public float getRelativeY(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseRelativeEvent> CREATOR; + } + + public static final class VirtualMouseRelativeEvent.Builder { + ctor public VirtualMouseRelativeEvent.Builder(); + method @NonNull public android.hardware.input.VirtualMouseRelativeEvent build(); + method @NonNull public android.hardware.input.VirtualMouseRelativeEvent.Builder setRelativeX(float); + method @NonNull public android.hardware.input.VirtualMouseRelativeEvent.Builder setRelativeY(float); + } + + public final class VirtualMouseScrollEvent implements android.os.Parcelable { + method public int describeContents(); + method public float getXAxisMovement(); + method public float getYAxisMovement(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseScrollEvent> CREATOR; + } + + public static final class VirtualMouseScrollEvent.Builder { + ctor public VirtualMouseScrollEvent.Builder(); + method @NonNull public android.hardware.input.VirtualMouseScrollEvent build(); + method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setXAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float); + method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setYAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float); + } + + public final class VirtualTouchEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getAction(); + method public float getMajorAxisSize(); + method public int getPointerId(); + method public float getPressure(); + method public int getToolType(); + method public float getX(); + method public float getY(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int ACTION_CANCEL = 3; // 0x3 + field public static final int ACTION_DOWN = 0; // 0x0 + field public static final int ACTION_MOVE = 2; // 0x2 + field public static final int ACTION_UP = 1; // 0x1 + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualTouchEvent> CREATOR; + field public static final int TOOL_TYPE_FINGER = 1; // 0x1 + field public static final int TOOL_TYPE_PALM = 5; // 0x5 + } + + public static final class VirtualTouchEvent.Builder { + ctor public VirtualTouchEvent.Builder(); + method @NonNull public android.hardware.input.VirtualTouchEvent build(); + method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setAction(int); + method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setMajorAxisSize(@FloatRange(from=0.0f) float); + method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setPointerId(int); + method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setPressure(@FloatRange(from=0.0f) float); + method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setToolType(int); + method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setX(float); + method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setY(float); + } + + public class VirtualTouchscreen implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent); + } + +} + package android.hardware.lights { public final class LightState implements android.os.Parcelable { @@ -5752,9 +5876,11 @@ package android.media.audiopolicy { method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException; method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>); method public int getFocusDuckingBehavior(); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack(); method public int getStatus(); method public boolean removeUidDeviceAffinity(int); method public boolean removeUserIdDeviceAffinity(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException; method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setRegistration(String); method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); @@ -13911,12 +14037,14 @@ package android.telephony.ims { } public final class RcsClientConfiguration implements android.os.Parcelable { - ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String); + ctor @Deprecated public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String); + ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String, boolean); method public int describeContents(); method @NonNull public String getClientVendor(); method @NonNull public String getClientVersion(); method @NonNull public String getRcsProfile(); method @NonNull public String getRcsVersion(); + method public boolean isRcsEnabledByUser(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsClientConfiguration> CREATOR; field public static final String RCS_PROFILE_1_0 = "UP_1.0"; @@ -14053,6 +14181,7 @@ package android.telephony.ims { field public static final int PUBLISH_STATE_NOT_PUBLISHED = 2; // 0x2 field public static final int PUBLISH_STATE_OK = 1; // 0x1 field public static final int PUBLISH_STATE_OTHER_ERROR = 6; // 0x6 + field public static final int PUBLISH_STATE_PUBLISHING = 7; // 0x7 field public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 4; // 0x4 field public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 5; // 0x5 field public static final int PUBLISH_STATE_VOICE_PROVISION_ERROR = 3; // 0x3 @@ -14315,6 +14444,7 @@ package android.telephony.ims.feature { package android.telephony.ims.stub { public interface CapabilityExchangeEventListener { + method public default void onPublishUpdated(int, @NonNull String, int, @NonNull String) throws android.telephony.ims.ImsException; method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException; method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException; method public void onUnpublish() throws android.telephony.ims.ImsException; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 37de0c8a7ecb..a92150079ec9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -685,6 +685,14 @@ package android.bluetooth { } +package android.companion { + + public abstract class CompanionDeviceService extends android.app.Service { + method public void onBindCompanionDeviceService(@NonNull android.content.Intent); + } + +} + package android.content { public final class AttributionSource implements android.os.Parcelable { diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index a642696b25ec..3573a5609bc6 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1394,6 +1394,12 @@ public abstract class AccessibilityService extends Service { * {@link AccessibilityService#onServiceConnected()} has not yet been * called) or the service has been disconnected, this method will * return a default value of {@code 1.0f}. + * </p> + * <p> + * <strong>Note:</strong> This legacy API gets the scale of full-screen + * magnification. To get the scale of the current controlling magnifier, + * use {@link #getMagnificationConfig} instead. + * </p> * * @return the current magnification scale */ @@ -1422,6 +1428,12 @@ public abstract class AccessibilityService extends Service { * {@link AccessibilityService#onServiceConnected()} has not yet been * called) or the service has been disconnected, this method will * return a default value of {@code 0.0f}. + * </p> + * <p> + * <strong>Note:</strong> This legacy API gets the center position of full-screen + * magnification. To get the magnification center of the current controlling magnifier, + * use {@link #getMagnificationConfig} instead. + * </p> * * @return the unscaled screen-relative X coordinate of the center of * the magnified region @@ -1451,6 +1463,12 @@ public abstract class AccessibilityService extends Service { * {@link AccessibilityService#onServiceConnected()} has not yet been * called) or the service has been disconnected, this method will * return a default value of {@code 0.0f}. + * </p> + * <p> + * <strong>Note:</strong> This legacy API gets the center position of full-screen + * magnification. To get the magnification center of the current controlling magnifier, + * use {@link #getMagnificationConfig} instead. + * </p> * * @return the unscaled screen-relative Y coordinate of the center of * the magnified region @@ -1571,6 +1589,11 @@ public abstract class AccessibilityService extends Service { * {@link AccessibilityService#onServiceConnected()} has not yet been * called) or the service has been disconnected, this method will have * no effect and return {@code false}. + * <p> + * <strong>Note:</strong> This legacy API sets the scale of full-screen + * magnification. To set the scale of the specified magnifier, + * use {@link #setMagnificationConfig} instead. + * </p> * * @param scale the magnification scale to set, must be >= 1 and <= 8 * @param animate {@code true} to animate from the current scale or @@ -1602,6 +1625,12 @@ public abstract class AccessibilityService extends Service { * {@link AccessibilityService#onServiceConnected()} has not yet been * called) or the service has been disconnected, this method will have * no effect and return {@code false}. + * </p> + * <p> + * <strong>Note:</strong> This legacy API sets the center of full-screen + * magnification. To set the center of the specified magnifier, + * use {@link #setMagnificationConfig} instead. + * </p> * * @param centerX the unscaled screen-relative X coordinate on which to * center the viewport diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 2c41e8d0925a..3cbae99224c7 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -23,6 +23,7 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Looper; +import android.os.SystemProperties; import android.os.Trace; import android.util.AndroidRuntimeException; import android.util.Log; @@ -74,6 +75,8 @@ import java.util.HashMap; public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback { private static final String TAG = "ValueAnimator"; private static final boolean DEBUG = false; + private static final boolean TRACE_ANIMATION_FRACTION = SystemProperties.getBoolean( + "persist.debug.animator.trace_fraction", false); /** * Internal constants @@ -1554,6 +1557,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio @CallSuper @UnsupportedAppUsage void animateValue(float fraction) { + if (TRACE_ANIMATION_FRACTION) { + Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(), + (int) (fraction * 1000)); + } fraction = mInterpolator.getInterpolation(fraction); mCurrentFraction = fraction; int numValues = mValues.length; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1c53fbd4de49..c0a8c1eb601e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2700,6 +2700,16 @@ public final class ActivityThread extends ClientTransactionHandler } } + void onSystemUiContextCleanup(ContextImpl context) { + synchronized (this) { + if (mDisplaySystemUiContexts == null) return; + final int index = mDisplaySystemUiContexts.indexOfValue(context); + if (index >= 0) { + mDisplaySystemUiContexts.removeAt(index); + } + } + } + public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { synchronized (this) { getSystemContext().installSystemApplicationInfo(info, classLoader); @@ -4093,12 +4103,12 @@ public final class ActivityThread extends ClientTransactionHandler } private void handleStartBinderTracking() { - Binder.enableTracing(); + Binder.enableStackTracking(); } private void handleStopBinderTrackingAndDump(ParcelFileDescriptor fd) { try { - Binder.disableTracing(); + Binder.disableStackTracking(); Binder.getTransactionTracker().writeTracesToFile(fd); } finally { IoUtils.closeQuietly(fd); @@ -6637,7 +6647,7 @@ public final class ActivityThread extends ClientTransactionHandler boolean isAppProfileable = isAppDebuggable || data.appInfo.isProfileable(); Trace.setAppTracingAllowed(isAppProfileable); if ((isAppProfileable || Build.IS_DEBUGGABLE) && data.enableBinderTracking) { - Binder.enableTracing(); + Binder.enableStackTracking(); } // Initialize heap profiling. diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4a7361efe4cc..885feb1f789a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3212,6 +3212,10 @@ class ContextImpl extends Context { final void performFinalCleanup(String who, String what) { //Log.i(TAG, "Cleanup up context: " + this); mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); + if (mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI + && mToken instanceof WindowTokenClient) { + mMainThread.onSystemUiContextCleanup(this); + } } @UnsupportedAppUsage diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 8f904b57474a..a2578d6d598a 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -520,6 +520,10 @@ interface IActivityManager { // descriptor. @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd); + + /** Enables server-side binder tracing for the calling uid. */ + void enableBinderTracing(); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void suppressResizeConfigChanges(boolean suppress); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index af907af2ce0d..779552f1bbed 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5808,6 +5808,7 @@ public class Notification implements Parcelable p, result); buildCustomContentIntoTemplate(mContext, standard, customContent, p, result); + makeHeaderExpanded(standard); return standard; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 81e6ae453277..5002a59440e9 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -131,6 +131,7 @@ import android.media.tv.interactive.TvIAppManager; import android.media.tv.tunerresourcemanager.ITunerResourceManager; import android.media.tv.tunerresourcemanager.TunerResourceManager; import android.net.ConnectivityFrameworkInitializer; +import android.net.ConnectivityFrameworkInitializerTiramisu; import android.net.EthernetManager; import android.net.IEthernetManager; import android.net.IIpSecService; @@ -146,8 +147,6 @@ import android.net.TetheringManager; import android.net.VpnManager; import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; -import android.net.nsd.INsdManager; -import android.net.nsd.NsdManager; import android.net.vcn.IVcnManagementService; import android.net.vcn.VcnManager; import android.net.wifi.WifiFrameworkInitializer; @@ -576,15 +575,6 @@ public final class SystemServiceRegistry { ctx.mMainThread.getHandler()); }}); - registerService(Context.NSD_SERVICE, NsdManager.class, - new CachedServiceFetcher<NsdManager>() { - @Override - public NsdManager createService(ContextImpl ctx) throws ServiceNotFoundException { - IBinder b = ServiceManager.getServiceOrThrow(Context.NSD_SERVICE); - INsdManager service = INsdManager.Stub.asInterface(b); - return new NsdManager(ctx.getOuterContext(), service); - }}); - registerService(Context.PEOPLE_SERVICE, PeopleManager.class, new CachedServiceFetcher<PeopleManager>() { @Override @@ -1547,6 +1537,7 @@ public final class SystemServiceRegistry { SupplementalProcessFrameworkInitializer.registerServiceWrappers(); UwbFrameworkInitializer.registerServiceWrappers(); SafetyCenterFrameworkInitializer.registerServiceWrappers(); + ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers(); } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 2b12f12a8ec0..ae1342593af7 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -776,6 +776,51 @@ public final class CompanionDeviceManager { } } + /** + * Notify the system that the given self-managed association has just 'appeared'. + * This causes the system to bind to the companion app to keep it running until the association + * is reported as 'disappeared' + * + * <p>This API is only available for the companion apps that manage the connectivity by + * themselves.</p> + * + * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association + * recorded by CompanionDeviceManager + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) + public void notifyDeviceAppeared(int associationId) { + try { + mService.notifyDeviceAppeared(associationId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notify the system that the given self-managed association has just 'disappeared'. + * This causes the system to unbind to the companion app. + * + * <p>This API is only available for the companion apps that manage the connectivity by + * themselves.</p> + * + * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association + * recorded by CompanionDeviceManager + + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) + public void notifyDeviceDisappeared(int associationId) { + try { + mService.notifyDeviceDisappeared(associationId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private boolean checkFeaturePresent() { boolean featurePresent = mService != null; if (!featurePresent && DEBUG) { diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 15266d62a963..3237f7ca340a 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -21,12 +21,14 @@ import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.TestApi; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.util.Log; + import com.android.internal.util.function.pooled.PooledLambda; import java.util.Objects; @@ -34,8 +36,9 @@ import java.util.Objects; /** * Service to be implemented by apps that manage a companion device. * - * System will keep this service bound whenever an associated device is nearby, - * ensuring app stays alive. + * System will keep this service bound whenever an associated device is nearby for Bluetooth + * devices or companion app manages the connectivity and reports disappeared, ensuring app stays + * alive * * An app must be {@link CompanionDeviceManager#associate associated} with at leas one device, * before it can take advantage of this service. @@ -43,6 +46,17 @@ import java.util.Objects; * You must declare this service in your manifest with an * intent-filter action of {@link #SERVICE_INTERFACE} and * permission of {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE} + * + * <p>If you want to declare more than one of these services, you must declare the meta-data in the + * service of your manifest with the corresponding name and value to true to indicate the + * primary service. + * Only the primary one will get the callback from + * {@link #onDeviceAppeared(AssociationInfo associationInfo)}.</p> + * + * Example: + * <meta-data + * android:name="primary" + * android:value="true" /> */ public abstract class CompanionDeviceService extends Service { @@ -52,13 +66,14 @@ public abstract class CompanionDeviceService extends Service { * An intent action for a service to be bound whenever this app's companion device(s) * are nearby. * - * <p>The app will be kept alive for as long as the device is nearby. + * <p>The app will be kept alive for as long as the device is nearby or companion app reports + * appeared. * If the app is not running at the time device gets connected, the app will be woken up.</p> * - * <p>Shortly after the device goes out of range, the service will be unbound, and the - * app will be eligible for cleanup, unless any other user-visible components are running.</p> + * <p>Shortly after the device goes out of range or the companion app reports disappeared, + * the service will be unbound, and the app will be eligible for cleanup, unless any other + * user-visible components are running.</p> * - * <p>An app shouldn't declare more than one of these services. * If running in background is not essential for the devices that this app can manage, * app should avoid declaring this service.</p> * @@ -73,9 +88,13 @@ public abstract class CompanionDeviceService extends Service { * Called by system whenever a device associated with this app is available. * * @param address the MAC address of the device + * @deprecated please override {@link #onDeviceAppeared(AssociationInfo)} instead. */ + @Deprecated @MainThread - public abstract void onDeviceAppeared(@NonNull String address); + public void onDeviceAppeared(@NonNull String address) { + // Do nothing. Companion apps can override this function. + } /** * Called by system whenever a device associated with this app stops being available. @@ -83,9 +102,13 @@ public abstract class CompanionDeviceService extends Service { * Usually this means the device goes out of range or is turned off. * * @param address the MAC address of the device + * @deprecated please override {@link #onDeviceDisappeared(AssociationInfo)} instead. */ + @Deprecated @MainThread - public abstract void onDeviceDisappeared(@NonNull String address); + public void onDeviceDisappeared(@NonNull String address) { + // Do nothing. Companion apps can override this function. + } /** * Called by system whenever the system dispatches a message to the app to send it to @@ -118,10 +141,35 @@ public abstract class CompanionDeviceService extends Service { companionDeviceManager.dispatchMessage(messageId, associationId, message); } + /** + * Called by system whenever a device associated with this app is connected. + * + * @param associationInfo A record for the companion device. + */ + @MainThread + public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) { + if (!associationInfo.isSelfManaged()) { + onDeviceAppeared(associationInfo.getDeviceMacAddressAsString()); + } + } + + /** + * Called by system whenever a device associated with this app is disconnected. + * + * @param associationInfo A record for the companion device. + */ + @MainThread + public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) { + if (!associationInfo.isSelfManaged()) { + onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString()); + } + } + @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) { + onBindCompanionDeviceService(intent); return mRemote; } Log.w(LOG_TAG, @@ -129,20 +177,26 @@ public abstract class CompanionDeviceService extends Service { return null; } + /** + * Used to track the state of Binder connection in CTS tests. + * @hide + */ + @TestApi + public void onBindCompanionDeviceService(@NonNull Intent intent) { + } + class Stub extends ICompanionDeviceService.Stub { @Override - public void onDeviceAppeared(String address) { - Handler.getMain().sendMessage(PooledLambda.obtainMessage( - CompanionDeviceService::onDeviceAppeared, - CompanionDeviceService.this, address)); + public void onDeviceAppeared(AssociationInfo associationInfo) { + Handler.getMain().post( + () -> CompanionDeviceService.this.onDeviceAppeared(associationInfo)); } @Override - public void onDeviceDisappeared(String address) { - Handler.getMain().sendMessage(PooledLambda.obtainMessage( - CompanionDeviceService::onDeviceDisappeared, - CompanionDeviceService.this, address)); + public void onDeviceDisappeared(AssociationInfo associationInfo) { + Handler.getMain().post( + () -> CompanionDeviceService.this.onDeviceDisappeared(associationInfo)); } public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) { diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 1558db22a003..68a6031f543f 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -65,5 +65,10 @@ interface ICompanionDeviceManager { void dispatchMessage(in int messageId, in int associationId, in byte[] message); void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId); + void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId); + + void notifyDeviceAppeared(int associationId); + + void notifyDeviceDisappeared(int associationId); } diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl index 25212a1f1030..4e453573f62e 100644 --- a/core/java/android/companion/ICompanionDeviceService.aidl +++ b/core/java/android/companion/ICompanionDeviceService.aidl @@ -16,9 +16,11 @@ package android.companion; +import android.companion.AssociationInfo; + /** @hide */ oneway interface ICompanionDeviceService { - void onDeviceAppeared(in String address); - void onDeviceDisappeared(in String address); + void onDeviceAppeared(in AssociationInfo associationInfo); + void onDeviceDisappeared(in AssociationInfo associationInfo); void onDispatchMessage(in int messageId, in int associationId, in byte[] message); } diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index dabc603bc47f..82ad15057fe3 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -16,6 +16,13 @@ package android.companion.virtual; +import android.graphics.Point; +import android.hardware.input.VirtualKeyEvent; +import android.hardware.input.VirtualMouseButtonEvent; +import android.hardware.input.VirtualMouseRelativeEvent; +import android.hardware.input.VirtualMouseScrollEvent; +import android.hardware.input.VirtualTouchEvent; + /** * Interface for a virtual device. * @@ -34,4 +41,29 @@ interface IVirtualDevice { * Closes the virtual device and frees all associated resources. */ void close(); + void createVirtualKeyboard( + int displayId, + String inputDeviceName, + int vendorId, + int productId, + IBinder token); + void createVirtualMouse( + int displayId, + String inputDeviceName, + int vendorId, + int productId, + IBinder token); + void createVirtualTouchscreen( + int displayId, + String inputDeviceName, + int vendorId, + int productId, + IBinder token, + in Point screenSize); + void unregisterInputDevice(IBinder token); + boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event); + boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event); + boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event); + boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event); + boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 590b10887c7f..0d024b1d3200 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -23,7 +23,13 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.companion.AssociationInfo; import android.content.Context; +import android.graphics.Point; +import android.hardware.display.VirtualDisplay; +import android.hardware.input.VirtualKeyboard; +import android.hardware.input.VirtualMouse; +import android.hardware.input.VirtualTouchscreen; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; /** @@ -73,6 +79,8 @@ public final class VirtualDeviceManager { * A virtual device has its own virtual display, audio output, microphone, and camera etc. The * creator of a virtual device can take the output from the virtual display and stream it over * to another device, and inject input events that are received from the remote device. + * + * TODO(b/204081582): Consider using a builder pattern for the input APIs. */ public static class VirtualDevice implements AutoCloseable { @@ -95,5 +103,88 @@ public final class VirtualDeviceManager { throw e.rethrowFromSystemServer(); } } + + /** + * Creates a virtual keyboard. + * + * @param display the display that the events inputted through this device should target + * @param inputDeviceName the name to call this input device + * @param vendorId the vendor id + * @param productId the product id + * @hide + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public VirtualKeyboard createVirtualKeyboard( + @NonNull VirtualDisplay display, + @NonNull String inputDeviceName, + int vendorId, + int productId) { + try { + final IBinder token = new Binder( + "android.hardware.input.VirtualKeyboard:" + inputDeviceName); + mVirtualDevice.createVirtualKeyboard(display.getDisplay().getDisplayId(), + inputDeviceName, vendorId, productId, token); + return new VirtualKeyboard(mVirtualDevice, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates a virtual mouse. + * + * @param display the display that the events inputted through this device should target + * @param inputDeviceName the name to call this input device + * @param vendorId the vendor id + * @param productId the product id + * @hide + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public VirtualMouse createVirtualMouse( + @NonNull VirtualDisplay display, + @NonNull String inputDeviceName, + int vendorId, + int productId) { + try { + final IBinder token = new Binder( + "android.hardware.input.VirtualMouse:" + inputDeviceName); + mVirtualDevice.createVirtualMouse(display.getDisplay().getDisplayId(), + inputDeviceName, vendorId, productId, token); + return new VirtualMouse(mVirtualDevice, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates a virtual touchscreen. + * + * @param display the display that the events inputted through this device should target + * @param inputDeviceName the name to call this input device + * @param vendorId the vendor id + * @param productId the product id + * @hide + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public VirtualTouchscreen createVirtualTouchscreen( + @NonNull VirtualDisplay display, + @NonNull String inputDeviceName, + int vendorId, + int productId) { + try { + final IBinder token = new Binder( + "android.hardware.input.VirtualTouchscreen:" + inputDeviceName); + final Point size = new Point(); + display.getDisplay().getSize(size); + mVirtualDevice.createVirtualTouchscreen(display.getDisplay().getDisplayId(), + inputDeviceName, vendorId, productId, token, size); + return new VirtualTouchscreen(mVirtualDevice, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 632d8e5e291c..b5d4f09144ee 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -80,6 +80,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -1942,6 +1943,16 @@ public class Resources { final Theme other = (Theme) o; return getKey().equals(other.getKey()); } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append('{'); + sb.append("id=0x").append(Integer.toHexString(getAppliedStyleResId())).append(", "); + sb.append("themes=").append(Arrays.deepToString(getTheme())); + sb.append('}'); + return sb.toString(); + } } static class ThemeKey implements Cloneable { diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index d6f55d65ead5..9eb9cd51284a 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -527,11 +527,12 @@ public class TypedArray implements AutoCloseable { final TypedValue value = mValue; getValueAt(index, value); throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + attrIndex + ": " + value); + "Failed to resolve attribute at index " + attrIndex + ": " + value + + ", theme=" + mTheme); } throw new UnsupportedOperationException("Can't convert value at index " + attrIndex - + " to color: type=0x" + Integer.toHexString(type)); + + " to color: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme); } /** @@ -561,7 +562,8 @@ public class TypedArray implements AutoCloseable { if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + index + ": " + value); + "Failed to resolve attribute at index " + index + ": " + value + + ", theme=" + mTheme); } return mResources.loadComplexColor(value, value.resourceId, mTheme); } @@ -596,7 +598,8 @@ public class TypedArray implements AutoCloseable { if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + index + ": " + value); + "Failed to resolve attribute at index " + index + ": " + value + + ", theme=" + mTheme); } return mResources.loadColorStateList(value, value.resourceId, mTheme); } @@ -637,11 +640,12 @@ public class TypedArray implements AutoCloseable { final TypedValue value = mValue; getValueAt(index, value); throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + attrIndex + ": " + value); + "Failed to resolve attribute at index " + attrIndex + ": " + value + + ", theme=" + mTheme); } throw new UnsupportedOperationException("Can't convert value at index " + attrIndex - + " to integer: type=0x" + Integer.toHexString(type)); + + " to integer: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme); } /** @@ -684,11 +688,12 @@ public class TypedArray implements AutoCloseable { final TypedValue value = mValue; getValueAt(index, value); throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + attrIndex + ": " + value); + "Failed to resolve attribute at index " + attrIndex + ": " + value + + ", theme=" + mTheme); } throw new UnsupportedOperationException("Can't convert value at index " + attrIndex - + " to dimension: type=0x" + Integer.toHexString(type)); + + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme); } /** @@ -732,11 +737,12 @@ public class TypedArray implements AutoCloseable { final TypedValue value = mValue; getValueAt(index, value); throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + attrIndex + ": " + value); + "Failed to resolve attribute at index " + attrIndex + ": " + value + + ", theme=" + mTheme); } throw new UnsupportedOperationException("Can't convert value at index " + attrIndex - + " to dimension: type=0x" + Integer.toHexString(type)); + + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme); } /** @@ -781,11 +787,12 @@ public class TypedArray implements AutoCloseable { final TypedValue value = mValue; getValueAt(index, value); throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + attrIndex + ": " + value); + "Failed to resolve attribute at index " + attrIndex + ": " + value + + ", theme=" + mTheme); } throw new UnsupportedOperationException("Can't convert value at index " + attrIndex - + " to dimension: type=0x" + Integer.toHexString(type)); + + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme); } /** @@ -825,11 +832,12 @@ public class TypedArray implements AutoCloseable { final TypedValue value = mValue; getValueAt(index, value); throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + attrIndex + ": " + value); + "Failed to resolve attribute at index " + attrIndex + ": " + value + + ", theme=" + mTheme); } throw new UnsupportedOperationException(getPositionDescription() - + ": You must supply a " + name + " attribute."); + + ": You must supply a " + name + " attribute." + ", theme=" + mTheme); } /** @@ -900,11 +908,12 @@ public class TypedArray implements AutoCloseable { final TypedValue value = mValue; getValueAt(index, value); throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + attrIndex + ": " + value); + "Failed to resolve attribute at index " + attrIndex + ": " + value + + ", theme=" + mTheme); } throw new UnsupportedOperationException("Can't convert value at index " + attrIndex - + " to fraction: type=0x" + Integer.toHexString(type)); + + " to fraction: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme); } /** @@ -996,7 +1005,8 @@ public class TypedArray implements AutoCloseable { if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + index + ": " + value); + "Failed to resolve attribute at index " + index + ": " + value + + ", theme=" + mTheme); } if (density > 0) { @@ -1032,7 +1042,8 @@ public class TypedArray implements AutoCloseable { if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( - "Failed to resolve attribute at index " + index + ": " + value); + "Failed to resolve attribute at index " + index + ": " + value + + ", theme=" + mTheme); } return mResources.getFont(value, value.resourceId); } diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index cad30dda9034..a4a8f313e3ba 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -25,6 +25,7 @@ import android.graphics.GraphicBuffer; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.view.SurfaceControl; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -129,6 +130,15 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { public static final long USAGE_GPU_SAMPLED_IMAGE = 1 << 8; /** Usage: The buffer will be written to by the GPU */ public static final long USAGE_GPU_COLOR_OUTPUT = 1 << 9; + /** + * The buffer will be used as a composer HAL overlay layer. + * + * This flag is currently only needed when using + * {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)} + * to set a buffer. In all other cases, the framework adds this flag + * internally to buffers that could be presented in a composer overlay. + */ + public static final long USAGE_COMPOSER_OVERLAY = 1 << 11; /** Usage: The buffer must not be used outside of a protected hardware path */ public static final long USAGE_PROTECTED_CONTENT = 1 << 14; /** Usage: The buffer will be read by a hardware video encoder */ diff --git a/core/java/android/hardware/input/VirtualKeyEvent.aidl b/core/java/android/hardware/input/VirtualKeyEvent.aidl new file mode 100644 index 000000000000..5b3ee0c985bd --- /dev/null +++ b/core/java/android/hardware/input/VirtualKeyEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +parcelable VirtualKeyEvent;
\ No newline at end of file diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java new file mode 100644 index 000000000000..d875156f5dc7 --- /dev/null +++ b/core/java/android/hardware/input/VirtualKeyEvent.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.KeyEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An event describing a keyboard interaction originating from a remote device. + * + * When the user presses a key, an {@code ACTION_DOWN} event should be reported. When the user + * releases the key, an {@code ACTION_UP} event should be reported. + * + * See {@link android.view.KeyEvent}. + * + * @hide + */ +@SystemApi +public final class VirtualKeyEvent implements Parcelable { + + /** @hide */ + public static final int ACTION_UNKNOWN = -1; + /** Action indicating the given key has been pressed. */ + public static final int ACTION_DOWN = KeyEvent.ACTION_DOWN; + /** Action indicating the previously pressed key has been lifted. */ + public static final int ACTION_UP = KeyEvent.ACTION_UP; + + /** @hide */ + @IntDef(prefix = { "ACTION_" }, value = { + ACTION_UNKNOWN, + ACTION_DOWN, + ACTION_UP, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Action { + } + + private final @Action int mAction; + private final int mKeyCode; + + private VirtualKeyEvent(@Action int action, int keyCode) { + mAction = action; + mKeyCode = keyCode; + } + + private VirtualKeyEvent(@NonNull Parcel parcel) { + mAction = parcel.readInt(); + mKeyCode = parcel.readInt(); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeInt(mAction); + parcel.writeInt(mKeyCode); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the key code associated with this event. + */ + public int getKeyCode() { + return mKeyCode; + } + + /** + * Returns the action associated with this event. + */ + public @Action int getAction() { + return mAction; + } + + /** + * Builder for {@link VirtualKeyEvent}. + */ + public static final class Builder { + + private @Action int mAction = ACTION_UNKNOWN; + private int mKeyCode = -1; + + /** + * Creates a {@link VirtualKeyEvent} object with the current builder configuration. + */ + public @NonNull VirtualKeyEvent build() { + if (mAction == ACTION_UNKNOWN || mKeyCode == -1) { + throw new IllegalArgumentException( + "Cannot build virtual key event with unset fields"); + } + return new VirtualKeyEvent(mAction, mKeyCode); + } + + /** + * Sets the Android key code of the event. The set of allowed characters include digits 0-9, + * characters A-Z, and standard punctuation, as well as numpad keys, function keys F1-F12, + * and meta keys (caps lock, shift, etc.). + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setKeyCode(int keyCode) { + mKeyCode = keyCode; + return this; + } + + /** + * Sets the action of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setAction(@Action int action) { + if (action != ACTION_DOWN && action != ACTION_UP) { + throw new IllegalArgumentException("Unsupported action type"); + } + mAction = action; + return this; + } + } + + public static final @NonNull Parcelable.Creator<VirtualKeyEvent> CREATOR = + new Parcelable.Creator<VirtualKeyEvent>() { + public VirtualKeyEvent createFromParcel(Parcel source) { + return new VirtualKeyEvent(source); + } + + public VirtualKeyEvent[] newArray(int size) { + return new VirtualKeyEvent[size]; + } + }; +} diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java new file mode 100644 index 000000000000..ee9b659e9521 --- /dev/null +++ b/core/java/android/hardware/input/VirtualKeyboard.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.os.IBinder; +import android.os.RemoteException; + +import java.io.Closeable; + +/** + * A virtual keyboard representing a key input mechanism on a remote device, such as a built-in + * keyboard on a laptop, a software keyboard on a tablet, or a keypad on a TV remote control. + * + * This registers an InputDevice that is interpreted like a physically-connected device and + * dispatches received events to it. + * + * @hide + */ +@SystemApi +public class VirtualKeyboard implements Closeable { + + private final IVirtualDevice mVirtualDevice; + private final IBinder mToken; + + /** @hide */ + public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) { + mVirtualDevice = virtualDevice; + mToken = token; + } + + @Override + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void close() { + try { + mVirtualDevice.unregisterInputDevice(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sends a key event to the system. + * + * @param event the event to send + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendKeyEvent(@NonNull VirtualKeyEvent event) { + try { + mVirtualDevice.sendKeyEvent(mToken, event); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java new file mode 100644 index 000000000000..6599dd2e28eb --- /dev/null +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.MotionEvent; + +import java.io.Closeable; + +/** + * A virtual mouse representing a relative input mechanism on a remote device, such as a mouse or + * trackpad. + * + * This registers an InputDevice that is interpreted like a physically-connected device and + * dispatches received events to it. + * + * @hide + */ +@SystemApi +public class VirtualMouse implements Closeable { + + private final IVirtualDevice mVirtualDevice; + private final IBinder mToken; + + /** @hide */ + public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) { + mVirtualDevice = virtualDevice; + mToken = token; + } + + @Override + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void close() { + try { + mVirtualDevice.unregisterInputDevice(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Send a mouse button event to the system. + * + * @param event the event + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) { + try { + mVirtualDevice.sendButtonEvent(mToken, event); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sends a scrolling event to the system. See {@link MotionEvent#AXIS_VSCROLL} and + * {@link MotionEvent#AXIS_SCROLL}. + * + * @param event the event + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) { + try { + mVirtualDevice.sendScrollEvent(mToken, event); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sends a relative movement event to the system. + * + * @param event the event + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) { + try { + mVirtualDevice.sendRelativeEvent(mToken, event); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl b/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl new file mode 100644 index 000000000000..ebcf5aad4066 --- /dev/null +++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +parcelable VirtualMouseButtonEvent;
\ No newline at end of file diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java new file mode 100644 index 000000000000..2e094cfb4e24 --- /dev/null +++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.MotionEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An event describing a mouse button click interaction originating from a remote device. + * + * @hide + */ +@SystemApi +public final class VirtualMouseButtonEvent implements Parcelable { + + /** @hide */ + public static final int ACTION_UNKNOWN = -1; + /** Action indicating the mouse button has been pressed. */ + public static final int ACTION_BUTTON_PRESS = MotionEvent.ACTION_BUTTON_PRESS; + /** Action indicating the mouse button has been released. */ + public static final int ACTION_BUTTON_RELEASE = MotionEvent.ACTION_BUTTON_RELEASE; + /** @hide */ + @IntDef(prefix = {"ACTION_"}, value = { + ACTION_UNKNOWN, + ACTION_BUTTON_PRESS, + ACTION_BUTTON_RELEASE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Action {} + + /** @hide */ + public static final int BUTTON_UNKNOWN = -1; + /** Action indicating the mouse button involved in this event is in the left position. */ + public static final int BUTTON_PRIMARY = MotionEvent.BUTTON_PRIMARY; + /** Action indicating the mouse button involved in this event is in the middle position. */ + public static final int BUTTON_TERTIARY = MotionEvent.BUTTON_TERTIARY; + /** Action indicating the mouse button involved in this event is in the right position. */ + public static final int BUTTON_SECONDARY = MotionEvent.BUTTON_SECONDARY; + /** + * Action indicating the mouse button involved in this event is intended to go back to the + * previous. + */ + public static final int BUTTON_BACK = MotionEvent.BUTTON_BACK; + /** + * Action indicating the mouse button involved in this event is intended to move forward to the + * next. + */ + public static final int BUTTON_FORWARD = MotionEvent.BUTTON_FORWARD; + /** @hide */ + @IntDef(prefix = {"BUTTON_"}, value = { + BUTTON_UNKNOWN, + BUTTON_PRIMARY, + BUTTON_TERTIARY, + BUTTON_SECONDARY, + BUTTON_BACK, + BUTTON_FORWARD, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Button {} + + private final @Action int mAction; + private final @Button int mButtonCode; + + private VirtualMouseButtonEvent(@Action int action, @Button int buttonCode) { + mAction = action; + mButtonCode = buttonCode; + } + + private VirtualMouseButtonEvent(@NonNull Parcel parcel) { + mAction = parcel.readInt(); + mButtonCode = parcel.readInt(); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeInt(mAction); + parcel.writeInt(mButtonCode); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the button code associated with this event. + */ + public @Button int getButtonCode() { + return mButtonCode; + } + + /** + * Returns the action associated with this event. + */ + public @Action int getAction() { + return mAction; + } + + /** + * Builder for {@link VirtualMouseButtonEvent}. + */ + public static final class Builder { + + private @Action int mAction = ACTION_UNKNOWN; + private @Button int mButtonCode = -1; + + /** + * Creates a {@link VirtualMouseButtonEvent} object with the current builder configuration. + */ + public @NonNull VirtualMouseButtonEvent build() { + if (mAction == ACTION_UNKNOWN || mButtonCode == -1) { + throw new IllegalArgumentException( + "Cannot build virtual mouse button event with unset fields"); + } + return new VirtualMouseButtonEvent(mAction, mButtonCode); + } + + /** + * Sets the button code of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setButtonCode(int buttonCode) { + if (buttonCode != BUTTON_PRIMARY + && buttonCode != BUTTON_TERTIARY + && buttonCode != BUTTON_SECONDARY + && buttonCode != BUTTON_BACK + && buttonCode != BUTTON_FORWARD) { + throw new IllegalArgumentException("Unsupported mouse button code"); + } + mButtonCode = buttonCode; + return this; + } + + /** + * Sets the action of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setAction(@Action int action) { + if (action != ACTION_BUTTON_PRESS && action != ACTION_BUTTON_RELEASE) { + throw new IllegalArgumentException("Unsupported mouse button action type"); + } + mAction = action; + return this; + } + } + + public static final @NonNull Parcelable.Creator<VirtualMouseButtonEvent> CREATOR = + new Parcelable.Creator<VirtualMouseButtonEvent>() { + public VirtualMouseButtonEvent createFromParcel(Parcel source) { + return new VirtualMouseButtonEvent(source); + } + + public VirtualMouseButtonEvent[] newArray(int size) { + return new VirtualMouseButtonEvent[size]; + } + }; +} diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl b/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl new file mode 100644 index 000000000000..1095858fde21 --- /dev/null +++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +parcelable VirtualMouseRelativeEvent;
\ No newline at end of file diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java new file mode 100644 index 000000000000..65ed1f2f6f3a --- /dev/null +++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An event describing a mouse movement interaction originating from a remote device. + * + * See {@link android.view.MotionEvent}. + * + * @hide + */ +@SystemApi +public final class VirtualMouseRelativeEvent implements Parcelable { + + private final float mRelativeX; + private final float mRelativeY; + + private VirtualMouseRelativeEvent(float relativeX, float relativeY) { + mRelativeX = relativeX; + mRelativeY = relativeY; + } + + private VirtualMouseRelativeEvent(@NonNull Parcel parcel) { + mRelativeX = parcel.readFloat(); + mRelativeY = parcel.readFloat(); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeFloat(mRelativeX); + parcel.writeFloat(mRelativeY); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the relative x-axis movement, in pixels. + */ + public float getRelativeX() { + return mRelativeX; + } + + /** + * Returns the relative x-axis movement, in pixels. + */ + public float getRelativeY() { + return mRelativeY; + } + + /** + * Builder for {@link VirtualMouseRelativeEvent}. + */ + public static final class Builder { + + private float mRelativeX; + private float mRelativeY; + + /** + * Creates a {@link VirtualMouseRelativeEvent} object with the current builder + * configuration. + */ + public @NonNull VirtualMouseRelativeEvent build() { + return new VirtualMouseRelativeEvent(mRelativeX, mRelativeY); + } + + /** + * Sets the relative x-axis movement, in pixels. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setRelativeX(float relativeX) { + mRelativeX = relativeX; + return this; + } + + /** + * Sets the relative y-axis movement, in pixels. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setRelativeY(float relativeY) { + mRelativeY = relativeY; + return this; + } + } + + public static final @NonNull Parcelable.Creator<VirtualMouseRelativeEvent> CREATOR = + new Parcelable.Creator<VirtualMouseRelativeEvent>() { + public VirtualMouseRelativeEvent createFromParcel(Parcel source) { + return new VirtualMouseRelativeEvent(source); + } + + public VirtualMouseRelativeEvent[] newArray(int size) { + return new VirtualMouseRelativeEvent[size]; + } + }; +} diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl b/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl new file mode 100644 index 000000000000..13177efcbb62 --- /dev/null +++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +parcelable VirtualMouseScrollEvent;
\ No newline at end of file diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java new file mode 100644 index 000000000000..1723259ba4b7 --- /dev/null +++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * An event describing a mouse scroll interaction originating from a remote device. + * + * See {@link android.view.MotionEvent}. + * + * @hide + */ +@SystemApi +public final class VirtualMouseScrollEvent implements Parcelable { + + private final float mXAxisMovement; + private final float mYAxisMovement; + + private VirtualMouseScrollEvent(float xAxisMovement, float yAxisMovement) { + mXAxisMovement = xAxisMovement; + mYAxisMovement = yAxisMovement; + } + + private VirtualMouseScrollEvent(@NonNull Parcel parcel) { + mXAxisMovement = parcel.readFloat(); + mYAxisMovement = parcel.readFloat(); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeFloat(mXAxisMovement); + parcel.writeFloat(mYAxisMovement); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values + * indicate scrolling upward; negative values, downward. + */ + public float getXAxisMovement() { + return mXAxisMovement; + } + + /** + * Returns the y-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values + * indicate scrolling towards the right; negative values, to the left. + */ + public float getYAxisMovement() { + return mYAxisMovement; + } + + /** + * Builder for {@link VirtualMouseScrollEvent}. + */ + public static final class Builder { + + private float mXAxisMovement; + private float mYAxisMovement; + + /** + * Creates a {@link VirtualMouseScrollEvent} object with the current builder configuration. + */ + public @NonNull VirtualMouseScrollEvent build() { + return new VirtualMouseScrollEvent(mXAxisMovement, mYAxisMovement); + } + + /** + * Sets the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values + * indicate scrolling upward; negative values, downward. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setXAxisMovement( + @FloatRange(from = -1.0f, to = 1.0f) float xAxisMovement) { + Preconditions.checkArgumentInRange(xAxisMovement, -1f, 1f, "xAxisMovement"); + mXAxisMovement = xAxisMovement; + return this; + } + + /** + * Sets the y-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values + * indicate scrolling towards the right; negative values, to the left. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setYAxisMovement( + @FloatRange(from = -1.0f, to = 1.0f) float yAxisMovement) { + Preconditions.checkArgumentInRange(yAxisMovement, -1f, 1f, "yAxisMovement"); + mYAxisMovement = yAxisMovement; + return this; + } + } + + public static final @NonNull Parcelable.Creator<VirtualMouseScrollEvent> CREATOR = + new Parcelable.Creator<VirtualMouseScrollEvent>() { + public VirtualMouseScrollEvent createFromParcel(Parcel source) { + return new VirtualMouseScrollEvent(source); + } + + public VirtualMouseScrollEvent[] newArray(int size) { + return new VirtualMouseScrollEvent[size]; + } + }; +} diff --git a/core/java/android/hardware/input/VirtualTouchEvent.aidl b/core/java/android/hardware/input/VirtualTouchEvent.aidl new file mode 100644 index 000000000000..03c82e3eef3a --- /dev/null +++ b/core/java/android/hardware/input/VirtualTouchEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +parcelable VirtualTouchEvent;
\ No newline at end of file diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java new file mode 100644 index 000000000000..c7450d8fa65d --- /dev/null +++ b/core/java/android/hardware/input/VirtualTouchEvent.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.MotionEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An event describing a touchscreen interaction originating from a remote device. + * + * The pointer id, tool type, action, and location are required; pressure and main axis size are + * optional. + * + * @hide + */ +@SystemApi +public final class VirtualTouchEvent implements Parcelable { + + /** @hide */ + public static final int TOOL_TYPE_UNKNOWN = MotionEvent.TOOL_TYPE_UNKNOWN; + /** Tool type indicating that the user's finger is the origin of the event. */ + public static final int TOOL_TYPE_FINGER = MotionEvent.TOOL_TYPE_FINGER; + /** + * Tool type indicating that a user's palm (or other input mechanism to be rejected) is the + * origin of the event. + */ + public static final int TOOL_TYPE_PALM = MotionEvent.TOOL_TYPE_PALM; + /** @hide */ + @IntDef(prefix = { "TOOL_TYPE_" }, value = { + TOOL_TYPE_UNKNOWN, + TOOL_TYPE_FINGER, + TOOL_TYPE_PALM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ToolType {} + + /** @hide */ + public static final int ACTION_UNKNOWN = -1; + /** Action indicating the tool has been pressed down to the touchscreen. */ + public static final int ACTION_DOWN = MotionEvent.ACTION_DOWN; + /** Action indicating the tool has been lifted from the touchscreen. */ + public static final int ACTION_UP = MotionEvent.ACTION_UP; + /** Action indicating the tool has been moved along the face of the touchscreen. */ + public static final int ACTION_MOVE = MotionEvent.ACTION_MOVE; + /** Action indicating the tool cancelled the current movement. */ + public static final int ACTION_CANCEL = MotionEvent.ACTION_CANCEL; + /** @hide */ + @IntDef(prefix = { "ACTION_" }, value = { + ACTION_UNKNOWN, + ACTION_DOWN, + ACTION_UP, + ACTION_MOVE, + ACTION_CANCEL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Action {} + + private final int mPointerId; + private final @ToolType int mToolType; + private final @Action int mAction; + private final float mX; + private final float mY; + private final float mPressure; + private final float mMajorAxisSize; + + private VirtualTouchEvent(int pointerId, @ToolType int toolType, @Action int action, + float x, float y, float pressure, float majorAxisSize) { + mPointerId = pointerId; + mToolType = toolType; + mAction = action; + mX = x; + mY = y; + mPressure = pressure; + mMajorAxisSize = majorAxisSize; + } + + private VirtualTouchEvent(@NonNull Parcel parcel) { + mPointerId = parcel.readInt(); + mToolType = parcel.readInt(); + mAction = parcel.readInt(); + mX = parcel.readFloat(); + mY = parcel.readFloat(); + mPressure = parcel.readFloat(); + mMajorAxisSize = parcel.readFloat(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPointerId); + dest.writeInt(mToolType); + dest.writeInt(mAction); + dest.writeFloat(mX); + dest.writeFloat(mY); + dest.writeFloat(mPressure); + dest.writeFloat(mMajorAxisSize); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the pointer id associated with this event. + */ + public int getPointerId() { + return mPointerId; + } + + /** + * Returns the tool type associated with this event. + */ + public @ToolType int getToolType() { + return mToolType; + } + + /** + * Returns the action associated with this event. + */ + public @Action int getAction() { + return mAction; + } + + /** + * Returns the x-axis location associated with this event. + */ + public float getX() { + return mX; + } + + /** + * Returns the y-axis location associated with this event. + */ + public float getY() { + return mY; + } + + /** + * Returns the pressure associated with this event. Returns {@link Float#NaN} if omitted. + */ + public float getPressure() { + return mPressure; + } + + /** + * Returns the major axis size associated with this event. Returns {@link Float#NaN} if omitted. + */ + public float getMajorAxisSize() { + return mMajorAxisSize; + } + + /** + * Builder for {@link VirtualTouchEvent}. + */ + public static final class Builder { + + private @ToolType int mToolType = TOOL_TYPE_UNKNOWN; + private int mPointerId = MotionEvent.INVALID_POINTER_ID; + private @Action int mAction = ACTION_UNKNOWN; + private float mX = Float.NaN; + private float mY = Float.NaN; + private float mPressure = Float.NaN; + private float mMajorAxisSize = Float.NaN; + + /** + * Creates a {@link VirtualTouchEvent} object with the current builder configuration. + */ + public @NonNull VirtualTouchEvent build() { + if (mToolType == TOOL_TYPE_UNKNOWN || mPointerId == MotionEvent.INVALID_POINTER_ID + || mAction == ACTION_UNKNOWN || Float.isNaN(mX) || Float.isNaN(mY)) { + throw new IllegalArgumentException( + "Cannot build virtual touch event with unset required fields"); + } + if ((mToolType == TOOL_TYPE_PALM && mAction != ACTION_CANCEL) + || (mAction == ACTION_CANCEL && mToolType != TOOL_TYPE_PALM)) { + throw new IllegalArgumentException( + "ACTION_CANCEL and TOOL_TYPE_PALM must always appear together"); + } + return new VirtualTouchEvent(mPointerId, mToolType, mAction, mX, mY, mPressure, + mMajorAxisSize); + } + + /** + * Sets the pointer id of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setPointerId(int pointerId) { + mPointerId = pointerId; + return this; + } + + /** + * Sets the tool type of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setToolType(@ToolType int toolType) { + if (toolType != TOOL_TYPE_FINGER && toolType != TOOL_TYPE_PALM) { + throw new IllegalArgumentException("Unsupported touch event tool type"); + } + mToolType = toolType; + return this; + } + + /** + * Sets the action of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setAction(@Action int action) { + if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE + && action != ACTION_CANCEL) { + throw new IllegalArgumentException("Unsupported touch event action type"); + } + mAction = action; + return this; + } + + /** + * Sets the x-axis location of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setX(float absX) { + mX = absX; + return this; + } + + /** + * Sets the y-axis location of the event. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setY(float absY) { + mY = absY; + return this; + } + + /** + * Sets the pressure of the event. This field is optional and can be omitted. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setPressure(@FloatRange(from = 0f) float pressure) { + if (pressure < 0f) { + throw new IllegalArgumentException("Touch event pressure cannot be negative"); + } + mPressure = pressure; + return this; + } + + /** + * Sets the major axis size of the event. This field is optional and can be omitted. + * + * @return this builder, to allow for chaining of calls + */ + public @NonNull Builder setMajorAxisSize(@FloatRange(from = 0f) float majorAxisSize) { + if (majorAxisSize < 0f) { + throw new IllegalArgumentException( + "Touch event major axis size cannot be negative"); + } + mMajorAxisSize = majorAxisSize; + return this; + } + } + + public static final @NonNull Parcelable.Creator<VirtualTouchEvent> CREATOR = + new Parcelable.Creator<VirtualTouchEvent>() { + public VirtualTouchEvent createFromParcel(Parcel source) { + return new VirtualTouchEvent(source); + } + public VirtualTouchEvent[] newArray(int size) { + return new VirtualTouchEvent[size]; + } + }; +} diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java new file mode 100644 index 000000000000..c8d602acaff6 --- /dev/null +++ b/core/java/android/hardware/input/VirtualTouchscreen.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.os.IBinder; +import android.os.RemoteException; + +import java.io.Closeable; + +/** + * A virtual touchscreen representing a touch-based display input mechanism on a remote device. + * + * This registers an InputDevice that is interpreted like a physically-connected device and + * dispatches received events to it. + * + * @hide + */ +@SystemApi +public class VirtualTouchscreen implements Closeable { + + private final IVirtualDevice mVirtualDevice; + private final IBinder mToken; + + /** @hide */ + public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) { + mVirtualDevice = virtualDevice; + mToken = token; + } + + @Override + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void close() { + try { + mVirtualDevice.unregisterInputDevice(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sends a touch event to the system. + * + * @param event the event to send + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendTouchEvent(@NonNull VirtualTouchEvent event) { + try { + mVirtualDevice.sendTouchEvent(mToken, event); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index b069fb336d55..e2d7847b85e9 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -22,9 +22,11 @@ import android.annotation.SystemApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; import android.util.ExceptionUtils; +import android.util.IntArray; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BinderCallHeavyHitterWatcher; import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener; import com.android.internal.os.BinderInternal; @@ -140,33 +142,55 @@ public class Binder implements IBinder { /** * Flag indicating whether we should be tracing transact calls. */ - private static volatile boolean sTracingEnabled = false; + private static volatile boolean sStackTrackingEnabled = false; + + private static final Object sTracingUidsWriteLock = new Object(); + private static volatile IntArray sTracingUidsImmutable = new IntArray(); /** - * Enable Binder IPC tracing. + * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to + * {@link TransactionTracker}. * * @hide */ - public static void enableTracing() { - sTracingEnabled = true; + public static void enableStackTracking() { + sStackTrackingEnabled = true; } /** - * Disable Binder IPC tracing. + * Disable Binder IPC stack tracking. * * @hide */ - public static void disableTracing() { - sTracingEnabled = false; + public static void disableStackTracking() { + sStackTrackingEnabled = false; + } + + /** + * @hide + */ + public static void enableTracingForUid(int uid) { + synchronized (sTracingUidsWriteLock) { + final IntArray copy = sTracingUidsImmutable.clone(); + copy.add(uid); + sTracingUidsImmutable = copy; + } } /** - * Check if binder transaction tracing is enabled. + * Check if binder transaction stack tracking is enabled. * * @hide */ - public static boolean isTracingEnabled() { - return sTracingEnabled; + public static boolean isStackTrackingEnabled() { + return sStackTrackingEnabled; + } + + /** + * @hide + */ + public static boolean isTracingEnabled(int callingUid) { + return sTracingUidsImmutable.indexOf(callingUid) != -1; } /** @@ -288,6 +312,9 @@ public class Binder implements IBinder { private IInterface mOwner; private String mDescriptor; + private volatile String[] mTransactionTraceNames = null; + private volatile String mSimpleDescriptor = null; + private static final int TRANSACTION_TRACE_NAME_ID_LIMIT = 1024; /** * Return the ID of the process that sent you the current transaction @@ -884,6 +911,53 @@ public class Binder implements IBinder { } /** + * @hide + */ + @VisibleForTesting + public final @NonNull String getTransactionTraceName(int transactionCode) { + if (mTransactionTraceNames == null) { + final String descriptor = getSimpleDescriptor(); + final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT); + final String[] transactionNames = new String[highestId + 1]; + final StringBuffer buf = new StringBuffer(); + for (int i = 0; i <= highestId; i++) { + String transactionName = getTransactionName(i + FIRST_CALL_TRANSACTION); + if (transactionName != null) { + buf.append(descriptor).append(':').append(transactionName); + } else { + buf.append(descriptor).append('#').append(i + FIRST_CALL_TRANSACTION); + } + transactionNames[i] = buf.toString(); + buf.setLength(0); + } + mTransactionTraceNames = transactionNames; + mSimpleDescriptor = descriptor; + } + final int index = transactionCode - FIRST_CALL_TRANSACTION; + if (index < 0 || index >= mTransactionTraceNames.length) { + return mSimpleDescriptor + "#" + transactionCode; + } + return mTransactionTraceNames[index]; + } + + private String getSimpleDescriptor() { + final int dot = mDescriptor.lastIndexOf("."); + if (dot > 0) { + // Strip the package name + return mDescriptor.substring(dot + 1); + } + return mDescriptor; + } + + /** + * @return The highest user-defined transaction id of all transactions. + * @hide + */ + public int getMaxTransactionId() { + return 0; + } + + /** * Implemented to call the more convenient version * {@link #dump(FileDescriptor, PrintWriter, String[])}. */ @@ -1181,7 +1255,8 @@ public class Binder implements IBinder { // Log any exceptions as warnings, don't silently suppress them. // If the call was {@link IBinder#FLAG_ONEWAY} then these exceptions // disappear into the ether. - final boolean tracingEnabled = Binder.isTracingEnabled(); + final boolean tracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_AIDL) && + (Binder.isStackTrackingEnabled() || Binder.isTracingEnabled(callingUid)); try { final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher; if (heavyHitterWatcher != null) { @@ -1189,9 +1264,7 @@ public class Binder implements IBinder { heavyHitterWatcher.onTransaction(callingUid, getClass(), code); } if (tracingEnabled) { - final String transactionName = getTransactionName(code); - Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" - + (transactionName != null ? transactionName : code)); + Trace.traceBegin(Trace.TRACE_TAG_AIDL, getTransactionTraceName(code)); } if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) { @@ -1226,7 +1299,7 @@ public class Binder implements IBinder { res = true; } finally { if (tracingEnabled) { - Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); + Trace.traceEnd(Trace.TRACE_TAG_AIDL); } if (observer != null) { // The parcel RPC headers have been called during onTransact so we can now access diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 483b549c5637..e9299207ea6b 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -545,7 +545,7 @@ public final class BinderProxy implements IBinder { } } - final boolean tracingEnabled = Binder.isTracingEnabled(); + final boolean tracingEnabled = Binder.isStackTrackingEnabled(); if (tracingEnabled) { final Throwable tr = new Throwable(); Binder.getTransactionTracker().addTrace(tr); diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 8292f26a8c6b..aa4b83a5c361 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -192,14 +192,15 @@ public class GraphicsEnvironment { // We only want to use ANGLE if the developer has explicitly chosen something other than // default driver. - final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE); - if (requested) { + final boolean forceAngle = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE); + final boolean forceNative = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE); + if (forceAngle || forceNative) { Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn); } final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName); - return requested || gameModeEnabledAngle; + return !forceNative && (forceAngle || gameModeEnabledAngle); } private int getVulkanVersion(PackageManager pm) { diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 83a6bc0ec99d..73ffd66486d2 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -44,7 +44,6 @@ import android.content.res.TypedArray; import android.graphics.BLASTBufferQueue; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; @@ -1930,9 +1929,7 @@ public abstract class WallpaperService extends Service { mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y); - GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer); - - t.setBuffer(mScreenshotSurfaceControl, graphicBuffer); + t.setBuffer(mScreenshotSurfaceControl, hardwareBuffer); t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace()); // Place on top everything else. t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE); diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 6984e4dfccc4..4789231b0404 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -874,6 +874,18 @@ public class StaticLayout extends Layout { ? Math.max(fmDescent, Math.round(descents[breakIndex])) : fmDescent; + // The fallback ascent/descent may be larger than top/bottom of the default font + // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected + // clipping. + if (fallbackLineSpacing) { + if (ascent < fmTop) { + fmTop = ascent; + } + if (descent > fmBottom) { + fmBottom = descent; + } + } + v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index b77265b0ebf6..7b28b8a607de 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -34,7 +34,7 @@ public class IntArray implements Cloneable { private int[] mValues; private int mSize; - private IntArray(int[] array, int size) { + private IntArray(int[] array, int size) { mValues = array; mSize = Preconditions.checkArgumentInRange(size, 0, array.length, "size"); } @@ -178,10 +178,8 @@ public class IntArray implements Cloneable { } @Override - public IntArray clone() throws CloneNotSupportedException { - final IntArray clone = (IntArray) super.clone(); - clone.mValues = mValues.clone(); - return clone; + public IntArray clone() { + return new IntArray(mValues.clone(), mSize); } /** diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index b8e50fc6adf2..adb8b86493d5 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1495,6 +1495,15 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int TOOL_TYPE_ERASER = 4; + /** + * Tool type constant: The tool is a palm and should be rejected. + * + * @see #getToolType + * + * @hide + */ + public static final int TOOL_TYPE_PALM = 5; + // NOTE: If you add a new tool type here you must also add it to: // native/include/android/input.h diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index af7d86cdd1d9..3b5270960c99 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -120,6 +120,8 @@ public final class SurfaceControl implements Parcelable { long relativeToObject, int zorder); private static native void nativeSetPosition(long transactionObj, long nativeObject, float x, float y); + private static native void nativeSetScale(long transactionObj, long nativeObject, + float x, float y); private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h); private static native void nativeSetTransparentRegionHint(long transactionObj, long nativeObject, Region region); @@ -202,9 +204,13 @@ public final class SurfaceControl implements Parcelable { private static native void nativeReparent(long transactionObj, long nativeObject, long newParentNativeObject); private static native void nativeSetBuffer(long transactionObj, long nativeObject, - GraphicBuffer buffer); + HardwareBuffer buffer); + private static native void nativeSetBufferTransform(long transactionObj, long nativeObject, + int transform); private static native void nativeSetColorSpace(long transactionObj, long nativeObject, int colorSpace); + private static native void nativeSetDamageRegion(long transactionObj, long nativeObject, + Region region); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); @@ -1333,7 +1339,6 @@ public final class SurfaceControl implements Parcelable { * Set the initial visibility for the SurfaceControl. * * @param hidden Whether the Surface is initially HIDDEN. - * @hide */ @NonNull public Builder setHidden(boolean hidden) { @@ -2840,16 +2845,38 @@ public final class SurfaceControl implements Parcelable { } /** - * @hide + * Sets the SurfaceControl to the specified position relative to the parent + * SurfaceControl + * + * @param sc The SurfaceControl to change position + * @param x the X position + * @param y the Y position + * @return this transaction */ - @UnsupportedAppUsage - public Transaction setPosition(SurfaceControl sc, float x, float y) { + @NonNull + public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) { checkPreconditions(sc); nativeSetPosition(mNativeObject, sc.mNativeObject, x, y); return this; } /** + * Sets the SurfaceControl to the specified scale with (0, 0) as the center point + * of the scale. + * + * @param sc The SurfaceControl to change scale + * @param scaleX the X scale + * @param scaleY the Y scale + * @return this transaction + */ + @NonNull + public Transaction setScale(@NonNull SurfaceControl sc, float scaleX, float scaleY) { + checkPreconditions(sc); + nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY); + return this; + } + + /** * Set the default buffer size for the SurfaceControl, if there is a * {@link Surface} associated with the control, then * this will be the default size for buffers dequeued from it. @@ -3056,7 +3083,9 @@ public final class SurfaceControl implements Parcelable { * @param sc SurfaceControl to set crop of. * @param crop Bounds of the crop to apply. * @hide + * @deprecated Use {@link #setCrop(SurfaceControl, Rect)} instead. */ + @Deprecated @UnsupportedAppUsage public Transaction setWindowCrop(SurfaceControl sc, Rect crop) { checkPreconditions(sc); @@ -3071,6 +3100,28 @@ public final class SurfaceControl implements Parcelable { } /** + * Bounds the surface and its children to the bounds specified. Size of the surface will be + * ignored and only the crop and buffer size will be used to determine the bounds of the + * surface. If no crop is specified and the surface has no buffer, the surface bounds is + * only constrained by the size of its parent bounds. + * + * @param sc SurfaceControl to set crop of. + * @param crop Bounds of the crop to apply. + * @return this This transaction for chaining + */ + public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) { + checkPreconditions(sc); + if (crop != null) { + nativeSetWindowCrop(mNativeObject, sc.mNativeObject, + crop.left, crop.top, crop.right, crop.bottom); + } else { + nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0); + } + + return this; + } + + /** * Same as {@link Transaction#setWindowCrop(SurfaceControl, Rect)} but sets the crop rect * top left at 0, 0. * @@ -3215,11 +3266,34 @@ public final class SurfaceControl implements Parcelable { } /** - * Sets the opacity of the surface. Setting the flag is equivalent to creating the - * Surface with the {@link #OPAQUE} flag. - * @hide + * Indicates whether the surface must be considered opaque, even if its pixel format is + * set to translucent. This can be useful if an application needs full RGBA 8888 support + * for instance but will still draw every pixel opaque. + * <p> + * This flag only determines whether opacity will be sampled from the alpha channel. + * Plane-alpha from calls to setAlpha() can still result in blended composition + * regardless of the opaque setting. + * + * Combined effects are (assuming a buffer format with an alpha channel): + * <ul> + * <li>OPAQUE + alpha(1.0) == opaque composition + * <li>OPAQUE + alpha(0.x) == blended composition + * <li>OPAQUE + alpha(0.0) == no composition + * <li>!OPAQUE + alpha(1.0) == blended composition + * <li>!OPAQUE + alpha(0.x) == blended composition + * <li>!OPAQUE + alpha(0.0) == no composition + * </ul> + * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true) + * were set automatically. + * + * @see Builder#setOpaque(boolean) + * + * @param sc The SurfaceControl to update + * @param isOpaque true if the buffer's alpha should be ignored, false otherwise + * @return this */ - public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) { + @NonNull + public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) { checkPreconditions(sc); if (isOpaque) { nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE); @@ -3498,14 +3572,57 @@ public final class SurfaceControl implements Parcelable { * created as type {@link #FX_SURFACE_BLAST} * * @hide + * @deprecated Use {@link #setBuffer(SurfaceControl, HardwareBuffer)} instead */ + @Deprecated public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) { + return setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer)); + } + + /** + * Updates the HardwareBuffer displayed for the SurfaceControl. + * + * Note that the buffer must be allocated with {@link HardwareBuffer#USAGE_COMPOSER_OVERLAY} + * as well as {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE} as the surface control might + * be composited using either an overlay or using the GPU. + * + * @param sc The SurfaceControl to update + * @param buffer The buffer to be displayed + * @return this + */ + public @NonNull Transaction setBuffer(@NonNull SurfaceControl sc, + @Nullable HardwareBuffer buffer) { checkPreconditions(sc); nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer); return this; } /** + * Sets the buffer transform that should be applied to the current buffer. + * + * @param sc The SurfaceControl to update + * @param transform The transform to apply to the buffer. + * @return this + */ + public @NonNull Transaction setBufferTransform(@NonNull SurfaceControl sc, + /* TODO: Mark the intdef */ int transform) { + checkPreconditions(sc); + nativeSetBufferTransform(mNativeObject, sc.mNativeObject, transform); + return this; + } + + /** + * Updates the region for the content on this surface updated in this transaction. + * + * If unspecified, the complete surface is assumed to be damaged. + */ + public @NonNull Transaction setDamageRegion(@NonNull SurfaceControl sc, + @Nullable Region region) { + nativeSetDamageRegion(mNativeObject, sc.mNativeObject, region); + return this; + } + + /** * Set the color space for the SurfaceControl. The supported color spaces are SRGB * and Display P3, other color spaces will be treated as SRGB. This can only be used for * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST} diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 3b15db2ded70..b85fe7c3aae2 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -836,20 +836,25 @@ public interface InputConnection { boolean beginBatchEdit(); /** - * Tell the editor that you are done with a batch edit previously - * initiated with {@link #beginBatchEdit}. This ends the latest - * batch only. - * - * <p><strong>IME authors:</strong> make sure you call this - * exactly once for each call to {@link #beginBatchEdit}.</p> - * - * <p><strong>Editor authors:</strong> please be careful about - * batch edit nesting. Updates still to be held back until the end - * of the last batch edit.</p> + * Tell the editor that you are done with a batch edit previously initiated with + * {@link #beginBatchEdit()}. This ends the latest batch only. + * + * <p><strong>IME authors:</strong> make sure you call this exactly once for each call to + * {@link #beginBatchEdit()}.</p> + * + * <p><strong>Editor authors:</strong> please be careful about batch edit nesting. Updates still + * to be held back until the end of the last batch edit. In case you are delegating this API + * call to the one obtained from + * {@link android.widget.EditText#onCreateInputConnection(EditorInfo)}, there was an off-by-one + * that had returned {@code true} when its nested batch edit count becomes {@code 0} as a result + * of invoking this API. This bug is fixed in {@link android.os.Build.VERSION_CODES#TIRAMISU}. + * </p> * - * @return true if there is still a batch edit in progress after closing - * the latest one (in other words, if the nesting count is > 0), false - * otherwise or if the input connection is no longer valid. + * @return For editor authors, you must return {@code true} if a batch edit is still in progress + * after closing the latest one (in other words, if the nesting count is still a + * positive number). Return {@code false} otherwise. For IME authors, you will + * always receive {@code true} as long as the request was sent to the editor, and + * receive {@code false} only if the input connection is no longer valid. */ boolean endBatchEdit(); diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index 8532763f9dad..2702c2d69c2b 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -437,7 +437,10 @@ public class UiTranslationController { if (view.getViewTranslationResponse() != null && view.getViewTranslationResponse().equals(response)) { if (callback instanceof TextViewTranslationCallback) { - if (((TextViewTranslationCallback) callback).isShowingTranslation()) { + TextViewTranslationCallback textViewCallback = + (TextViewTranslationCallback) callback; + if (textViewCallback.isShowingTranslation() + || textViewCallback.isAnimationRunning()) { if (DEBUG) { Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId + ". Ignoring."); diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java index 4a78f3ee6fac..942be21b1ade 100644 --- a/core/java/android/widget/TextViewTranslationCallback.java +++ b/core/java/android/widget/TextViewTranslationCallback.java @@ -46,6 +46,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { private TranslationTransformationMethod mTranslationTransformation; private boolean mIsShowingTranslation = false; + private boolean mAnimationRunning = false; private boolean mIsTextPaddingEnabled = false; private CharSequence mPaddedText; private int mAnimationDurationMillis = 250; // default value @@ -92,6 +93,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { (TextView) view, () -> { mIsShowingTranslation = true; + mAnimationRunning = false; // TODO(b/178353965): well-handle setTransformationMethod. ((TextView) view).setTransformationMethod(transformation); }); @@ -124,6 +126,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { (TextView) view, () -> { mIsShowingTranslation = false; + mAnimationRunning = false; ((TextView) view).setTransformationMethod(transformation); }); if (!TextUtils.isEmpty(mContentDescription)) { @@ -162,6 +165,13 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { return mIsShowingTranslation; } + /** + * Returns whether the view is running animation to show or hide the translation. + */ + public boolean isAnimationRunning() { + return mAnimationRunning; + } + @Override public void enableContentPadding() { mIsTextPaddingEnabled = true; @@ -230,6 +240,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { mAnimator.end(); // Note: mAnimator is now null; do not use again here. } + mAnimationRunning = true; int fadedOutColor = colorWithAlpha(view.getCurrentTextColor(), 0); mAnimator = ValueAnimator.ofArgb(view.getCurrentTextColor(), fadedOutColor); mAnimator.addUpdateListener( diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index b331a9e81e27..4ba7ef26e9cb 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -15,7 +15,6 @@ */ package android.window; -import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets; import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; @@ -222,14 +221,7 @@ public class WindowTokenClient extends IWindowToken.Stub { () -> windowContext.dispatchConfigurationChanged(newConfig)); } - // Dispatch onConfigurationChanged only if there's a significant public change to - // make it compatible with the original behavior. - final Configuration[] sizeConfigurations = context.getResources() - .getSizeConfigurations(); - final SizeConfigurationBuckets buckets = sizeConfigurations != null - ? new SizeConfigurationBuckets(sizeConfigurations) : null; - final int diff = diffPublicWithSizeBuckets(mConfiguration, newConfig, buckets); - + final int diff = mConfiguration.diffPublicOnly(newConfig); if (shouldReportConfigChange && diff != 0 && context instanceof WindowProviderService) { final WindowProviderService windowProviderService = (WindowProviderService) context; diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index 410e68bf3581..29bb3111d83e 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -89,7 +89,7 @@ public final class EditableInputConnection extends BaseInputConnection // contribution to mTextView's nested batch edit count is zero. mTextView.endBatchEdit(); mBatchEditNesting--; - return true; + return mBatchEditNesting > 0; } } return false; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index fb5fded923f2..67d0c52960e0 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -597,6 +597,14 @@ static void nativeSetPosition(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setPosition(ctrl, x, y); } +static void nativeSetScale(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, + jfloat xScale, jfloat yScale) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setMatrix(ctrl, xScale, 0, 0, yScale); +} + static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jobject sourceObj, jobject dstObj, jlong orientation) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -620,9 +628,19 @@ static void nativeSetBuffer(JNIEnv* env, jclass clazz, jlong transactionObj, jlo jobject bufferObject) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); - sp<GraphicBuffer> buffer( - android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject)); - transaction->setBuffer(ctrl, buffer); + sp<GraphicBuffer> graphicBuffer(GraphicBuffer::fromAHardwareBuffer( + android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, bufferObject))); + transaction->setBuffer(ctrl, graphicBuffer); +} + +static void nativeSetBufferTransform(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint transform) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setTransform(ctrl, transform); + bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) == + NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY; + transaction->setTransformToDisplayInverse(ctrl, transformToInverseDisplay); } static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, @@ -740,6 +758,37 @@ static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong tran } } +static void nativeSetDamageRegion(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jobject regionObj) { + SurfaceControl* const surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject); + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + if (regionObj == nullptr) { + transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION); + return; + } + + graphics::RegionIterator iterator(env, regionObj); + if (!iterator.isValid()) { + transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION); + return; + } + + Region region; + while (!iterator.isDone()) { + ARect rect = iterator.getRect(); + region.orSelf(static_cast<const Rect&>(rect)); + iterator.next(); + } + + if (region.getBounds().isEmpty()) { + transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION); + return; + } + + transaction->setSurfaceDamageRegion(surfaceControl, region); +} + static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jfloat alpha) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -1905,10 +1954,14 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetRelativeLayer }, {"nativeSetPosition", "(JJFF)V", (void*)nativeSetPosition }, + {"nativeSetScale", "(JJFF)V", + (void*)nativeSetScale }, {"nativeSetSize", "(JJII)V", (void*)nativeSetSize }, {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V", (void*)nativeSetTransparentRegionHint }, + { "nativeSetDamageRegion", "(JJLandroid/graphics/Region;)V", + (void*)nativeSetDamageRegion }, {"nativeSetAlpha", "(JJF)V", (void*)nativeSetAlpha }, {"nativeSetColor", "(JJ[F)V", @@ -2018,8 +2071,9 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetDisplayedContentSample }, {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V", (void*)nativeSetGeometry }, - {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V", + {"nativeSetBuffer", "(JJLandroid/hardware/HardwareBuffer;)V", (void*)nativeSetBuffer }, + {"nativeSetBufferTransform", "(JJI)V", (void*) nativeSetBufferTransform}, {"nativeSetColorSpace", "(JJI)V", (void*)nativeSetColorSpace }, {"nativeSyncInputWindows", "(J)V", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4c367326b670..08948773f599 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2066,7 +2066,7 @@ <permission android:name="android.permission.BLUETOOTH_PRIVILEGED" android:protectionLevel="signature|privileged" /> - <!-- Control access to email providers exclusively for Bluetooth + <!-- @SystemApi Control access to email providers exclusively for Bluetooth @hide --> <permission android:name="android.permission.BLUETOOTH_MAP" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5b7d1b9607d5..ddec469f63ad 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2383,7 +2383,7 @@ <!-- If supported and enabled, are dreams activated when asleep and charging? (by default) --> <bool name="config_dreamsActivatedOnSleepByDefault">false</bool> <!-- ComponentName of the default dream (Settings.Secure.DEFAULT_SCREENSAVER_COMPONENT) --> - <string name="config_dreamsDefaultComponent" translatable="false">com.google.android.deskclock/com.android.deskclock.Screensaver</string> + <string name="config_dreamsDefaultComponent" translatable="false">com.android.deskclock/com.android.deskclock.Screensaver</string> <!-- Are we allowed to dream while not plugged in? --> <bool name="config_dreamsEnabledOnBattery">false</bool> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 64b303b724dc..32d72b37b8af 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -23,6 +23,7 @@ android_test { ], aidl: { + generate_get_transaction_name: true, local_include_dirs: ["aidl"], }, diff --git a/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java new file mode 100644 index 000000000000..37cc9b70dd1b --- /dev/null +++ b/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.testng.Assert.assertThrows; + +import android.view.KeyEvent; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualKeyEventTest { + + @Test + public void keyEvent_emptyBuilder() { + assertThrows(IllegalArgumentException.class, () -> new VirtualKeyEvent.Builder().build()); + } + + @Test + public void keyEvent_noKeyCode() { + assertThrows(IllegalArgumentException.class, + () -> new VirtualKeyEvent.Builder().setAction(VirtualKeyEvent.ACTION_DOWN).build()); + } + + @Test + public void keyEvent_noAction() { + assertThrows(IllegalArgumentException.class, + () -> new VirtualKeyEvent.Builder().setKeyCode(KeyEvent.KEYCODE_A).build()); + } + + @Test + public void keyEvent_created() { + final VirtualKeyEvent event = new VirtualKeyEvent.Builder() + .setAction(VirtualKeyEvent.ACTION_DOWN) + .setKeyCode(KeyEvent.KEYCODE_A).build(); + assertWithMessage("Incorrect key code").that(event.getKeyCode()).isEqualTo( + KeyEvent.KEYCODE_A); + assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo( + VirtualKeyEvent.ACTION_DOWN); + } +} diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java new file mode 100644 index 000000000000..789e0bb2ff56 --- /dev/null +++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.testng.Assert.assertThrows; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualMouseButtonEventTest { + + @Test + public void buttonEvent_emptyBuilder() { + assertThrows(IllegalArgumentException.class, + () -> new VirtualMouseButtonEvent.Builder().build()); + } + + @Test + public void buttonEvent_noButtonCode() { + assertThrows(IllegalArgumentException.class, () -> new VirtualMouseButtonEvent.Builder() + .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE).build()); + } + + @Test + public void buttonEvent_noAction() { + assertThrows(IllegalArgumentException.class, () -> new VirtualMouseButtonEvent.Builder() + .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK).build()); + } + + @Test + public void buttonEvent_created() { + final VirtualMouseButtonEvent event = new VirtualMouseButtonEvent.Builder() + .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS) + .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK).build(); + assertWithMessage("Incorrect button code").that(event.getButtonCode()).isEqualTo( + VirtualMouseButtonEvent.BUTTON_BACK); + assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo( + VirtualMouseButtonEvent.ACTION_BUTTON_PRESS); + } +} diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java new file mode 100644 index 000000000000..c0508162869b --- /dev/null +++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import static com.google.common.truth.Truth.assertWithMessage; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualMouseRelativeEventTest { + + @Test + public void relativeEvent_created() { + final VirtualMouseRelativeEvent event = new VirtualMouseRelativeEvent.Builder() + .setRelativeX(-5f) + .setRelativeY(8f).build(); + assertWithMessage("Incorrect x value").that(event.getRelativeX()).isEqualTo(-5f); + assertWithMessage("Incorrect y value").that(event.getRelativeY()).isEqualTo(8f); + } +} diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java new file mode 100644 index 000000000000..2259c740da7e --- /dev/null +++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.testng.Assert.assertThrows; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualMouseScrollEventTest { + + @Test + public void scrollEvent_xOutOfRange() { + assertThrows(IllegalArgumentException.class, () -> new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(1.5f) + .setYAxisMovement(1.0f)); + } + + @Test + public void scrollEvent_yOutOfRange() { + assertThrows(IllegalArgumentException.class, () -> new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(0.5f) + .setYAxisMovement(1.1f)); + } + + @Test + public void scrollEvent_created() { + final VirtualMouseScrollEvent event = new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(-1f) + .setYAxisMovement(1f).build(); + assertWithMessage("Incorrect x value").that(event.getXAxisMovement()).isEqualTo(-1f); + assertWithMessage("Incorrect y value").that(event.getYAxisMovement()).isEqualTo(1f); + } +} + diff --git a/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java new file mode 100644 index 000000000000..3f504a00773c --- /dev/null +++ b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.testng.Assert.assertThrows; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualTouchEventTest { + + @Test + public void touchEvent_emptyBuilder() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder().build()); + } + + @Test + public void touchEvent_noAction() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder() + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .setX(0f) + .setY(1f) + .setPointerId(1) + .build()); + } + + @Test + public void touchEvent_noPointerId() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_DOWN) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .setX(0f) + .setY(1f) + .build()); + } + + @Test + public void touchEvent_noToolType() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_DOWN) + .setX(0f) + .setY(1f) + .setPointerId(1) + .build()); + } + + @Test + public void touchEvent_noX() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_DOWN) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .setY(1f) + .setPointerId(1) + .build()); + } + + + @Test + public void touchEvent_noY() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_DOWN) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .setX(0f) + .setPointerId(1) + .build()); + } + + @Test + public void touchEvent_created() { + final VirtualTouchEvent event = new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_DOWN) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .setX(0f) + .setY(1f) + .setPointerId(1) + .build(); + assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo( + VirtualTouchEvent.ACTION_DOWN); + assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo( + VirtualTouchEvent.TOOL_TYPE_FINGER); + assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f); + assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f); + assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1); + } + + @Test + public void touchEvent_created_withPressureAndAxis() { + final VirtualTouchEvent event = new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_DOWN) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .setX(0f) + .setY(1f) + .setPointerId(1) + .setPressure(0.5f) + .setMajorAxisSize(10f) + .build(); + assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo( + VirtualTouchEvent.ACTION_DOWN); + assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo( + VirtualTouchEvent.TOOL_TYPE_FINGER); + assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f); + assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f); + assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1); + assertWithMessage("Incorrect pressure").that(event.getPressure()).isEqualTo(0.5f); + assertWithMessage("Incorrect major axis size").that(event.getMajorAxisSize()).isEqualTo( + 10f); + } + + @Test + public void touchEvent_cancelUsedImproperly() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_CANCEL) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .setX(0f) + .setY(1f) + .setPointerId(1) + .build()); + } + + @Test + public void touchEvent_palmUsedImproperly() { + assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_MOVE) + .setToolType(VirtualTouchEvent.TOOL_TYPE_PALM) + .setX(0f) + .setY(1f) + .setPointerId(1) + .build()); + } + + @Test + public void touchEvent_palmAndCancelUsedProperly() { + final VirtualTouchEvent event = new VirtualTouchEvent.Builder() + .setAction(VirtualTouchEvent.ACTION_CANCEL) + .setToolType(VirtualTouchEvent.TOOL_TYPE_PALM) + .setX(0f) + .setY(1f) + .setPointerId(1) + .setPressure(0.5f) + .setMajorAxisSize(10f) + .build(); + assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo( + VirtualTouchEvent.ACTION_CANCEL); + assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo( + VirtualTouchEvent.TOOL_TYPE_PALM); + assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f); + assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f); + assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1); + assertWithMessage("Incorrect pressure").that(event.getPressure()).isEqualTo(0.5f); + assertWithMessage("Incorrect major axis size").that(event.getMajorAxisSize()).isEqualTo( + 10f); + } +} diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java index 4c5141537c6a..5f54b093e5e5 100644 --- a/core/tests/coretests/src/android/os/AidlTest.java +++ b/core/tests/coretests/src/android/os/AidlTest.java @@ -27,11 +27,12 @@ import java.util.List; public class AidlTest extends TestCase { private IAidlTest mRemote; + private AidlObject mLocal; @Override protected void setUp() throws Exception { super.setUp(); - AidlObject mLocal = new AidlObject(); + mLocal = new AidlObject(); mRemote = IAidlTest.Stub.asInterface(mLocal); } @@ -417,5 +418,24 @@ public class AidlTest extends TestCase { } assertEquals(good, true); } + + @SmallTest + public void testGetTransactionName() throws Exception { + assertEquals(15, mLocal.getMaxTransactionId()); + + assertEquals("booleanArray", + mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_booleanArray)); + assertEquals("voidSecurityException", + mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_voidSecurityException)); + assertEquals("parcelableIn", + mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_parcelableIn)); + + assertEquals("IAidlTest:booleanArray", + mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_booleanArray)); + assertEquals("IAidlTest:voidSecurityException", + mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_voidSecurityException)); + assertEquals("IAidlTest:parcelableIn", + mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_parcelableIn)); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index af19bd0a79ac..b8e8b0114b47 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -295,11 +295,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) { + SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, + secondaryContainer, splitRule); + // Remove container later to prevent pinning escaping toast showing in lock task mode. if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); } - SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, - secondaryContainer, splitRule); mSplitContainers.add(splitContainer); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 81be21cbd7aa..ade573132eef 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -112,8 +112,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryContainer.setLastRequestedBounds(secondaryRectBounds); // Set adjacent to each other so that the containers below will be invisible. - setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), - secondaryContainer.getTaskFragmentToken(), rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -149,8 +148,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryActivity, secondaryRectBounds, primaryContainer); // Set adjacent to each other so that the containers below will be invisible. - setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), - secondaryContainer.getTaskFragmentToken(), rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -269,8 +267,22 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); - setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), - secondaryContainer.getTaskFragmentToken(), rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + } + + private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) { + final Rect parentBounds = getParentContainerBounds(primaryContainer); + // Clear adjacent TaskFragments if the container is shown in fullscreen, or the + // secondaryContainer could not be finished. + if (!shouldShowSideBySide(parentBounds, splitRule)) { + setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), + null /* secondary */, null /* splitRule */); + } else { + setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), + secondaryContainer.getTaskFragmentToken(), splitRule); + } } /** diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml index 22cd384e1be0..26848b13a1bc 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml +++ b/libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml @@ -16,6 +16,6 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="@color/size_compat_background"/> - <corners android:radius="@dimen/size_compat_hint_corner_radius"/> + <solid android:color="@color/compat_controls_background"/> + <corners android:radius="@dimen/compat_hint_corner_radius"/> </shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/drawable/compat_hint_point.xml index af9063a94afb..0e0ca37aaf25 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml +++ b/libs/WindowManager/Shell/res/drawable/compat_hint_point.xml @@ -15,11 +15,11 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/size_compat_hint_point_width" + android:width="@dimen/compat_hint_point_width" android:height="8dp" android:viewportWidth="10" android:viewportHeight="8"> <path - android:fillColor="@color/size_compat_background" + android:fillColor="@color/compat_controls_background" android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml index 18caa3582537..ab74e43472c3 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml @@ -20,16 +20,16 @@ android:viewportWidth="48" android:viewportHeight="48"> <path - android:fillColor="@color/size_compat_background" + android:fillColor="@color/compat_controls_background" android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" /> <group android:translateX="12" android:translateY="12"> <path - android:fillColor="@color/size_compat_text" + android:fillColor="@color/compat_controls_text" android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/> <path - android:fillColor="@color/size_compat_text" + android:fillColor="@color/compat_controls_text" android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/> </group> </vector> diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml new file mode 100644 index 000000000000..c04e258ea784 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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:orientation="vertical" + android:clipToPadding="false" + android:paddingEnd="@dimen/compat_hint_padding_end" + android:paddingBottom="5dp" + android:clickable="true"> + + <TextView + android:id="@+id/compat_mode_hint_text" + android:layout_width="188dp" + android:layout_height="wrap_content" + android:lineSpacingExtra="4sp" + android:background="@drawable/compat_hint_bubble" + android:padding="16dp" + android:textAlignment="viewStart" + android:textColor="@color/compat_controls_text" + android:textSize="14sp"/> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:src="@drawable/compat_hint_point" + android:paddingHorizontal="@dimen/compat_hint_corner_radius" + android:contentDescription="@null"/> + +</LinearLayout> diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml index 82ebee263a64..6f946b25eaa5 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml +++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml @@ -14,10 +14,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.wm.shell.sizecompatui.SizeCompatRestartButton +<com.android.wm.shell.compatui.CompatUILayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="bottom|end"> + + <include android:id="@+id/size_compat_hint" + layout="@layout/compat_mode_hint"/> <FrameLayout android:layout_width="@dimen/size_compat_button_width" @@ -36,4 +41,4 @@ </FrameLayout> -</com.android.wm.shell.sizecompatui.SizeCompatRestartButton> +</com.android.wm.shell.compatui.CompatUILayout> diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml deleted file mode 100644 index d0e7c42dbf8b..000000000000 --- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.wm.shell.sizecompatui.SizeCompatHintPopup - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <FrameLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:clipToPadding="false" - android:paddingBottom="5dp"> - - <LinearLayout - android:id="@+id/size_compat_hint_popup" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - android:clickable="true"> - - <TextView - android:layout_width="188dp" - android:layout_height="wrap_content" - android:lineSpacingExtra="4sp" - android:background="@drawable/size_compat_hint_bubble" - android:padding="16dp" - android:text="@string/restart_button_description" - android:textAlignment="viewStart" - android:textColor="@color/size_compat_text" - android:textSize="14sp"/> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:src="@drawable/size_compat_hint_point" - android:paddingHorizontal="@dimen/size_compat_hint_corner_radius" - android:contentDescription="@null"/> - - </LinearLayout> - - </FrameLayout> - -</com.android.wm.shell.sizecompatui.SizeCompatHintPopup> diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index 23a21724e43d..cf596f7d15dc 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -30,9 +30,9 @@ <color name="bubbles_dark">@color/GM2_grey_800</color> <color name="bubbles_icon_tint">@color/GM2_grey_700</color> - <!-- Size Compat Restart Button --> - <color name="size_compat_background">@android:color/system_neutral1_800</color> - <color name="size_compat_text">@android:color/system_neutral1_50</color> + <!-- Compat controls UI --> + <color name="compat_controls_background">@android:color/system_neutral1_800</color> + <color name="compat_controls_text">@android:color/system_neutral1_50</color> <!-- GM2 colors --> <color name="GM2_grey_200">#E8EAED</color> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 9e77578eafd8..18e91f41a698 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -206,11 +206,15 @@ <!-- The height of the size compat restart button including padding. --> <dimen name="size_compat_button_height">64dp</dimen> - <!-- The radius of the corners of the size compat hint bubble. --> - <dimen name="size_compat_hint_corner_radius">28dp</dimen> + <!-- The radius of the corners of the compat hint bubble. --> + <dimen name="compat_hint_corner_radius">28dp</dimen> - <!-- The width of the size compat hint point. --> - <dimen name="size_compat_hint_point_width">10dp</dimen> + <!-- The width of the compat hint point. --> + <dimen name="compat_hint_point_width">10dp</dimen> + + <!-- The end padding for the compat hint. Computed as (size_compat_button_width / 2 + - compat_hint_corner_radius - compat_hint_point_width /2). --> + <dimen name="compat_hint_padding_end">7dp</dimen> <!-- The width of the brand image on staring surface. --> <dimen name="starting_surface_brand_image_width">200dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 8e98b82088dc..8b3a35688f11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -52,8 +52,8 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.startingsurface.StartingWindowController; import java.io.PrintWriter; @@ -69,7 +69,7 @@ import java.util.function.Consumer; * TODO(b/167582004): may consider consolidating this class and TaskOrganizer */ public class ShellTaskOrganizer extends TaskOrganizer implements - SizeCompatUIController.SizeCompatUICallback { + CompatUIController.CompatUICallback { // Intentionally using negative numbers here so the positive numbers can be used // for task id specific listeners that will be added later. @@ -98,9 +98,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements default void onTaskInfoChanged(RunningTaskInfo taskInfo) {} default void onTaskVanished(RunningTaskInfo taskInfo) {} default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {} - /** Whether this task listener supports size compat UI. */ - default boolean supportSizeCompatUI() { - // All TaskListeners should support size compat except PIP. + /** Whether this task listener supports compat UI. */ + default boolean supportCompatUI() { + // All TaskListeners should support compat UI except PIP. return true; } /** Attaches the a child window surface to the task surface. */ @@ -159,11 +159,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private StartingWindowController mStartingWindow; /** - * In charge of showing size compat UI. Can be {@code null} if device doesn't support size + * In charge of showing compat UI. Can be {@code null} if device doesn't support size * compat. */ @Nullable - private final SizeCompatUIController mSizeCompatUI; + private final CompatUIController mCompatUI; @Nullable private final Optional<RecentTasksController> mRecentTasks; @@ -172,32 +172,32 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private RunningTaskInfo mLastFocusedTaskInfo; public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { - this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */, + this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */, Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable - SizeCompatUIController sizeCompatUI) { - this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + CompatUIController compatUI) { + this(null /* taskOrganizerController */, mainExecutor, context, compatUI, Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable - SizeCompatUIController sizeCompatUI, + CompatUIController compatUI, Optional<RecentTasksController> recentTasks) { - this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + this(null /* taskOrganizerController */, mainExecutor, context, compatUI, recentTasks); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable SizeCompatUIController sizeCompatUI, + Context context, @Nullable CompatUIController compatUI, Optional<RecentTasksController> recentTasks) { super(taskOrganizerController, mainExecutor); - mSizeCompatUI = sizeCompatUI; + mCompatUI = compatUI; mRecentTasks = recentTasks; - if (sizeCompatUI != null) { - sizeCompatUI.setSizeCompatUICallback(this); + if (compatUI != null) { + compatUI.setCompatUICallback(this); } } @@ -428,7 +428,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements listener.onTaskAppeared(info.getTaskInfo(), info.getLeash()); } notifyLocusVisibilityIfNeeded(info.getTaskInfo()); - notifySizeCompatUI(info.getTaskInfo(), listener); + notifyCompatUI(info.getTaskInfo(), listener); } /** @@ -459,8 +459,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } notifyLocusVisibilityIfNeeded(taskInfo); if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) { - // Notify the size compat UI if the listener or task info changed. - notifySizeCompatUI(taskInfo, newListener); + // Notify the compat UI if the listener or task info changed. + notifyCompatUI(taskInfo, newListener); } if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) { // Notify the recent tasks when a task changes windowing modes @@ -504,8 +504,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements listener.onTaskVanished(taskInfo); } notifyLocusVisibilityIfNeeded(taskInfo); - // Pass null for listener to remove the size compat UI on this task if there is any. - notifySizeCompatUI(taskInfo, null /* taskListener */); + // Pass null for listener to remove the compat UI on this task if there is any. + notifyCompatUI(taskInfo, null /* taskListener */); // Notify the recent tasks that a task has been removed mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo)); } @@ -618,28 +618,28 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** - * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task + * Notifies {@link CompatUIController} about the compat info changed on the give Task * to update the UI accordingly. * * @param taskInfo the new Task info * @param taskListener listener to handle the Task Surface placement. {@code null} if task is * vanished. */ - private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) { - if (mSizeCompatUI == null) { + private void notifyCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) { + if (mCompatUI == null) { return; } - // The task is vanished or doesn't support size compat UI, notify to remove size compat UI + // The task is vanished or doesn't support compat UI, notify to remove compat UI // on this Task if there is any. - if (taskListener == null || !taskListener.supportSizeCompatUI() + if (taskListener == null || !taskListener.supportCompatUI() || !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) { - mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, + mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, null /* taskConfig */, null /* taskListener */); return; } - mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, + mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, taskInfo.configuration, taskListener); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index d92e2ccc77bd..686fbbfd6f7c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -15,23 +15,22 @@ */ package com.android.wm.shell.bubbles; -import static android.graphics.Paint.ANTI_ALIAS_FLAG; -import static android.graphics.Paint.DITHER_FLAG; -import static android.graphics.Paint.FILTER_BITMAP_FLAG; - +import android.annotation.DrawableRes; import android.annotation.Nullable; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.PathParser; +import android.view.Gravity; import android.view.View; import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; import android.widget.ImageView; import com.android.launcher3.icons.DotRenderer; @@ -47,7 +46,7 @@ import java.util.EnumSet; * Badge = the icon associated with the app that created this bubble, this will show work profile * badge if appropriate. */ -public class BadgedImageView extends ImageView { +public class BadgedImageView extends FrameLayout { /** Same value as Launcher3 dot code */ public static final float WHITE_SCRIM_ALPHA = 0.54f; @@ -74,6 +73,9 @@ public class BadgedImageView extends ImageView { private final EnumSet<SuppressionFlag> mDotSuppressionFlags = EnumSet.of(SuppressionFlag.FLYOUT_VISIBLE); + private final ImageView mBubbleIcon; + private final ImageView mAppIcon; + private float mDotScale = 0f; private float mAnimatingToDotScale = 0f; private boolean mDotIsAnimating = false; @@ -86,7 +88,6 @@ public class BadgedImageView extends ImageView { private DotRenderer.DrawParams mDrawParams; private int mDotColor; - private Paint mPaint = new Paint(ANTI_ALIAS_FLAG); private Rect mTempBounds = new Rect(); public BadgedImageView(Context context) { @@ -104,6 +105,17 @@ public class BadgedImageView extends ImageView { public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + + mBubbleIcon = new ImageView(context); + addView(mBubbleIcon); + mAppIcon = new ImageView(context); + addView(mAppIcon); + + final TypedArray ta = mContext.obtainStyledAttributes(attrs, new int[]{android.R.attr.src}, + defStyleAttr, defStyleRes); + mBubbleIcon.setImageResource(ta.getResourceId(0, 0)); + ta.recycle(); + mDrawParams = new DotRenderer.DrawParams(); setFocusable(true); @@ -135,7 +147,6 @@ public class BadgedImageView extends ImageView { public void showDotAndBadge(boolean onLeft) { removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK); animateDotBadgePositions(onLeft); - } public void hideDotAndBadge(boolean onLeft) { @@ -149,6 +160,7 @@ public class BadgedImageView extends ImageView { */ public void setRenderedBubble(BubbleViewProvider bubble) { mBubble = bubble; + mBubbleIcon.setImageBitmap(bubble.getBubbleIcon()); if (mDotSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK)) { hideBadge(); } else { @@ -176,6 +188,20 @@ public class BadgedImageView extends ImageView { mDotRenderer.draw(canvas, mDrawParams); } + /** + * Set drawable resource shown as the icon + */ + public void setIconImageResource(@DrawableRes int drawable) { + mBubbleIcon.setImageResource(drawable); + } + + /** + * Get icon drawable + */ + public Drawable getIconDrawable() { + return mBubbleIcon.getDrawable(); + } + /** Adds a dot suppression flag, updating dot visibility if needed. */ void addDotSuppressionFlag(SuppressionFlag flag) { if (mDotSuppressionFlags.add(flag)) { @@ -279,7 +305,6 @@ public class BadgedImageView extends ImageView { showBadge(); } - /** Whether to draw the dot in onDraw(). */ private boolean shouldDrawDot() { // Always render the dot if it's animating, since it could be animating out. Otherwise, show @@ -325,29 +350,28 @@ public class BadgedImageView extends ImageView { void showBadge() { Bitmap badge = mBubble.getAppBadge(); if (badge == null) { - setImageBitmap(mBubble.getBubbleIcon()); + mAppIcon.setVisibility(GONE); return; } - Canvas bubbleCanvas = new Canvas(); - Bitmap noBadgeBubble = mBubble.getBubbleIcon(); - Bitmap bubble = noBadgeBubble.copy(noBadgeBubble.getConfig(), /* isMutable */ true); - bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG)); - bubbleCanvas.setBitmap(bubble); - final int bubbleSize = bubble.getWidth(); + final int bubbleSize = mBubble.getBubbleIcon().getWidth(); final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize); - Rect dest = new Rect(); + + FrameLayout.LayoutParams appIconParams = (LayoutParams) mAppIcon.getLayoutParams(); + appIconParams.height = badgeSize; + appIconParams.width = badgeSize; if (mOnLeft) { - dest.set(0, bubbleSize - badgeSize, badgeSize, bubbleSize); + appIconParams.gravity = Gravity.BOTTOM | Gravity.LEFT; } else { - dest.set(bubbleSize - badgeSize, bubbleSize - badgeSize, bubbleSize, bubbleSize); + appIconParams.gravity = Gravity.BOTTOM | Gravity.RIGHT; } - bubbleCanvas.drawBitmap(badge, null /* src */, dest, mPaint); - bubbleCanvas.setBitmap(null); - setImageBitmap(bubble); + mAppIcon.setLayoutParams(appIconParams); + + mAppIcon.setImageBitmap(badge); + mAppIcon.setVisibility(VISIBLE); } void hideBadge() { - setImageBitmap(mBubble.getBubbleIcon()); + mAppIcon.setVisibility(GONE); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 519a856538c7..cd635c10fd8e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -328,6 +328,7 @@ public class BubbleData { if (prevBubble == null) { // Create a new bubble bubble.setSuppressFlyout(suppressFlyout); + bubble.markUpdatedAt(mTimeSource.currentTimeMillis()); doAdd(bubble); trim(); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index 5161092fb96c..a175929cf498 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -66,7 +66,7 @@ class BubbleOverflow( updateResources() getExpandedView()?.applyThemeAttrs() // Apply inset and new style to fresh icon drawable. - getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button) + getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button) updateBtnTheme() } @@ -89,19 +89,19 @@ class BubbleOverflow( dotColor = colorAccent val shapeColor = res.getColor(android.R.color.system_accent1_1000) - overflowBtn?.drawable?.setTint(shapeColor) + overflowBtn?.iconDrawable?.setTint(shapeColor) val iconFactory = BubbleIconFactory(context) // Update bitmap - val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset) + val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset) bitmap = iconFactory.createBadgedIconBitmap( AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon // Update dot path dotPath = PathParser.createPathFromPathData( res.getString(com.android.internal.R.string.config_icon_mask)) - val scale = iconFactory.normalizer.getScale(iconView!!.drawable, + val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable, null /* outBounds */, null /* path */, null /* outMaskShape */) val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f val matrix = Matrix() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java index eea6e3cb35db..c4bd73ba1b4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java @@ -16,7 +16,6 @@ package com.android.wm.shell.common; -import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; import android.graphics.Rect; import android.view.SurfaceControl; @@ -63,8 +62,6 @@ public class ScreenshotUtils { if (buffer == null || buffer.getHardwareBuffer() == null) { return; } - final GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( - buffer.getHardwareBuffer()); mScreenshot = new SurfaceControl.Builder() .setName("ScreenshotUtils screenshot") .setFormat(PixelFormat.TRANSLUCENT) @@ -73,7 +70,7 @@ public class ScreenshotUtils { .setBLASTLayer() .build(); - mTransaction.setBuffer(mScreenshot, graphicBuffer); + mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer()); mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace()); mTransaction.reparent(mScreenshot, mSurfaceControl); mTransaction.setLayer(mScreenshot, mLayer); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 040fffae3e84..b8ac87fa5d4c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -93,7 +93,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private final InsetsState mInsetsState = new InsetsState(); private Context mContext; - private DividerSnapAlgorithm mDividerSnapAlgorithm; + @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm; private WindowContainerToken mWinToken1; private WindowContainerToken mWinToken2; private int mDividePosition; @@ -294,20 +294,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mSplitLayoutHandler.onLayoutSizeChanging(this); } - void setDividePosition(int position) { + void setDividePosition(int position, boolean applyLayoutChange) { mDividePosition = position; updateBounds(mDividePosition); - mSplitLayoutHandler.onLayoutSizeChanged(this); + if (applyLayoutChange) { + mSplitLayoutHandler.onLayoutSizeChanged(this); + } } - /** Sets divide position base on the ratio within root bounds. */ + /** Updates divide position and split bounds base on the ratio within root bounds. */ public void setDivideRatio(float ratio) { final int position = isLandscape() ? mRootBounds.left + (int) (mRootBounds.width() * ratio) : mRootBounds.top + (int) (mRootBounds.height() * ratio); - DividerSnapAlgorithm.SnapTarget snapTarget = + final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position); - setDividePosition(snapTarget.position); + setDividePosition(snapTarget.position, false /* applyLayoutChange */); } /** Resets divider position. */ @@ -336,7 +338,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange break; default: flingDividePosition(currentPosition, snapTarget.position, - () -> setDividePosition(snapTarget.position)); + () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)); break; } } @@ -389,7 +391,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange @Override public void onAnimationCancel(Animator animation) { - setDividePosition(to); + setDividePosition(to, true /* applyLayoutChange */); } }); animator.start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java index a703114194a0..99dbfe01964c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.sizecompatui; +package com.android.wm.shell.compatui; import com.android.wm.shell.common.annotations.ExternalThread; /** - * Interface to engage size compat UI. + * Interface to engage compat UI. */ @ExternalThread -public interface SizeCompatUI { +public interface CompatUI { /** - * Called when the keyguard occluded state changes. Removes all size compat UIs if the + * Called when the keyguard occluded state changes. Removes all compat UIs if the * keyguard is now occluded. * @param occluded indicates if the keyguard is now occluded. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index e06070ab12e5..e0b23873a980 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.sizecompatui; +package com.android.wm.shell.compatui; import android.annotation.Nullable; import android.content.Context; @@ -48,20 +48,20 @@ import java.util.function.Predicate; /** * Controls to show/update restart-activity buttons on Tasks based on whether the foreground - * activities are in size compatibility mode. + * activities are in compatibility mode. */ -public class SizeCompatUIController implements OnDisplaysChangedListener, +public class CompatUIController implements OnDisplaysChangedListener, DisplayImeController.ImePositionProcessor { /** Callback for size compat UI interaction. */ - public interface SizeCompatUICallback { + public interface CompatUICallback { /** Called when the size compat restart button appears. */ void onSizeCompatRestartButtonAppeared(int taskId); /** Called when the size compat restart button is clicked. */ void onSizeCompatRestartButtonClicked(int taskId); } - private static final String TAG = "SizeCompatUIController"; + private static final String TAG = "CompatUIController"; /** Whether the IME is shown on display id. */ private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1); @@ -71,7 +71,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, new SparseArray<>(0); /** The showing UIs by task id. */ - private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0); + private final SparseArray<CompatUIWindowManager> mActiveLayouts = new SparseArray<>(0); /** Avoid creating display context frequently for non-default display. */ private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); @@ -82,17 +82,17 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, private final DisplayImeController mImeController; private final SyncTransactionQueue mSyncQueue; private final ShellExecutor mMainExecutor; - private final SizeCompatUIImpl mImpl = new SizeCompatUIImpl(); + private final CompatUIImpl mImpl = new CompatUIImpl(); - private SizeCompatUICallback mCallback; + private CompatUICallback mCallback; /** Only show once automatically in the process life. */ private boolean mHasShownHint; - /** Indicates if the keyguard is currently occluded, in which case size compat UIs shouldn't + /** Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't * be shown. */ private boolean mKeyguardOccluded; - public SizeCompatUIController(Context context, + public CompatUIController(Context context, DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, @@ -108,35 +108,36 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, mImeController.addPositionProcessor(this); } - public SizeCompatUI asSizeCompatUI() { + /** Returns implementation of {@link CompatUI}. */ + public CompatUI asCompatUI() { return mImpl; } /** Sets the callback for UI interactions. */ - public void setSizeCompatUICallback(SizeCompatUICallback callback) { + public void setCompatUICallback(CompatUICallback callback) { mCallback = callback; } /** - * Called when the Task info changed. Creates and updates the size compat UI if there is an + * Called when the Task info changed. Creates and updates the compat UI if there is an * activity in size compat, or removes the UI if there is no size compat activity. * * @param displayId display the task and activity are in. * @param taskId task the activity is in. - * @param taskConfig task config to place the size compat UI with. + * @param taskConfig task config to place the compat UI with. * @param taskListener listener to handle the Task Surface placement. */ - public void onSizeCompatInfoChanged(int displayId, int taskId, + public void onCompatInfoChanged(int displayId, int taskId, @Nullable Configuration taskConfig, @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (taskConfig == null || taskListener == null) { - // Null token means the current foreground activity is not in size compatibility mode. + // Null token means the current foreground activity is not in compatibility mode. removeLayout(taskId); } else if (mActiveLayouts.contains(taskId)) { // UI already exists, update the UI layout. updateLayout(taskId, taskConfig, taskListener); } else { - // Create a new size compat UI. + // Create a new compat UI. createLayout(displayId, taskId, taskConfig, taskListener); } } @@ -151,7 +152,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, mDisplayContextCache.remove(displayId); removeOnInsetsChangedListener(displayId); - // Remove all size compat UIs on the removed display. + // Remove all compat UIs on the removed display. final List<Integer> toRemoveTaskIds = new ArrayList<>(); forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId())); for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) { @@ -194,7 +195,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, mDisplaysWithIme.remove(displayId); } - // Hide the size compat UIs when input method is showing. + // Hide the compat UIs when input method is showing. forAllLayoutsOnDisplay(displayId, layout -> layout.updateVisibility(showOnDisplay(displayId))); } @@ -202,7 +203,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, @VisibleForTesting void onKeyguardOccludedChanged(boolean occluded) { mKeyguardOccluded = occluded; - // Hide the size compat UIs when keyguard is occluded. + // Hide the compat UIs when keyguard is occluded. forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId()))); } @@ -222,34 +223,34 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, return; } - final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig, - taskListener); - mActiveLayouts.put(taskId, layout); - layout.createSizeCompatButton(showOnDisplay(displayId)); + final CompatUIWindowManager compatUIWindowManager = + createLayout(context, displayId, taskId, taskConfig, taskListener); + mActiveLayouts.put(taskId, compatUIWindowManager); + compatUIWindowManager.createLayout(showOnDisplay(displayId)); } @VisibleForTesting - SizeCompatUILayout createLayout(Context context, int displayId, int taskId, + CompatUIWindowManager createLayout(Context context, int displayId, int taskId, Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) { - final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, mCallback, context, - taskConfig, taskId, taskListener, mDisplayController.getDisplayLayout(displayId), - mHasShownHint); + final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context, + taskConfig, mSyncQueue, mCallback, taskId, taskListener, + mDisplayController.getDisplayLayout(displayId), mHasShownHint); // Only show hint for the first time. mHasShownHint = true; - return layout; + return compatUIWindowManager; } private void updateLayout(int taskId, Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) { - final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + final CompatUIWindowManager layout = mActiveLayouts.get(taskId); if (layout == null) { return; } - layout.updateSizeCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId())); + layout.updateCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId())); } private void removeLayout(int taskId) { - final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + final CompatUIWindowManager layout = mActiveLayouts.get(taskId); if (layout != null) { layout.release(); mActiveLayouts.remove(taskId); @@ -275,19 +276,19 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, return context; } - private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) { + private void forAllLayoutsOnDisplay(int displayId, Consumer<CompatUIWindowManager> callback) { forAllLayouts(layout -> layout.getDisplayId() == displayId, callback); } - private void forAllLayouts(Consumer<SizeCompatUILayout> callback) { + private void forAllLayouts(Consumer<CompatUIWindowManager> callback) { forAllLayouts(layout -> true, callback); } - private void forAllLayouts(Predicate<SizeCompatUILayout> condition, - Consumer<SizeCompatUILayout> callback) { + private void forAllLayouts(Predicate<CompatUIWindowManager> condition, + Consumer<CompatUIWindowManager> callback) { for (int i = 0; i < mActiveLayouts.size(); i++) { final int taskId = mActiveLayouts.keyAt(i); - final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + final CompatUIWindowManager layout = mActiveLayouts.get(taskId); if (layout != null && condition.test(layout)) { callback.accept(layout); } @@ -298,11 +299,11 @@ public class SizeCompatUIController implements OnDisplaysChangedListener, * The interface for calls from outside the Shell, within the host process. */ @ExternalThread - private class SizeCompatUIImpl implements SizeCompatUI { + private class CompatUIImpl implements CompatUI { @Override public void onKeyguardOccludedChanged(boolean occluded) { mMainExecutor.execute(() -> { - SizeCompatUIController.this.onKeyguardOccludedChanged(occluded); + CompatUIController.this.onKeyguardOccludedChanged(occluded); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java new file mode 100644 index 000000000000..ea4f20968438 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.wm.shell.R; + +/** + * Container for compat UI controls. + */ +public class CompatUILayout extends LinearLayout { + + private CompatUIWindowManager mWindowManager; + + public CompatUILayout(Context context) { + this(context, null); + } + + public CompatUILayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CompatUILayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public CompatUILayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + void inject(CompatUIWindowManager windowManager) { + mWindowManager = windowManager; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + // Need to relayout after changes like hiding / showing a hint since they affect size. + // Doing this directly in setSizeCompatHintVisibility can result in flaky animation. + mWindowManager.relayout(); + } + + void setSizeCompatHintVisibility(boolean show) { + final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint); + int visibility = show ? View.VISIBLE : View.GONE; + if (sizeCompatHint.getVisibility() == visibility) { + return; + } + sizeCompatHint.setVisibility(visibility); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + final ImageButton restartButton = findViewById(R.id.size_compat_restart_button); + restartButton.setOnClickListener(view -> mWindowManager.onRestartButtonClicked()); + restartButton.setOnLongClickListener(view -> { + mWindowManager.onRestartButtonLongClicked(); + return true; + }); + + final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint); + ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text)) + .setText(R.string.restart_button_description); + sizeCompatHint.setOnClickListener(view -> setSizeCompatHintVisibility(/* show= */ false)); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java new file mode 100644 index 000000000000..997ad04e3b57 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Binder; +import android.util.Log; +import android.view.IWindow; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceSession; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * Holds view hierarchy of a root surface and helps to inflate and manage layout for compat + * controls. + */ +class CompatUIWindowManager extends WindowlessWindowManager { + + private static final String TAG = "CompatUIWindowManager"; + + private final SyncTransactionQueue mSyncQueue; + private final CompatUIController.CompatUICallback mCallback; + private final int mDisplayId; + private final int mTaskId; + private final Rect mStableBounds; + + private Context mContext; + private Configuration mTaskConfig; + private ShellTaskOrganizer.TaskListener mTaskListener; + private DisplayLayout mDisplayLayout; + + @VisibleForTesting + boolean mShouldShowHint; + + @Nullable + @VisibleForTesting + CompatUILayout mCompatUILayout; + + @Nullable + private SurfaceControlViewHost mViewHost; + @Nullable + private SurfaceControl mLeash; + + CompatUIWindowManager(Context context, Configuration taskConfig, + SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback, + int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, + boolean hasShownHint) { + super(taskConfig, null /* rootSurface */, null /* hostInputToken */); + mContext = context; + mSyncQueue = syncQueue; + mCallback = callback; + mTaskConfig = taskConfig; + mDisplayId = mContext.getDisplayId(); + mTaskId = taskId; + mTaskListener = taskListener; + mDisplayLayout = displayLayout; + mShouldShowHint = !hasShownHint; + mStableBounds = new Rect(); + mDisplayLayout.getStableBounds(mStableBounds); + } + + @Override + public void setConfiguration(Configuration configuration) { + super.setConfiguration(configuration); + mContext = mContext.createConfigurationContext(configuration); + } + + @Override + protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. + final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) + .setContainerLayer() + .setName("CompatUILeash") + .setHidden(false) + .setCallsite("CompatUIWindowManager#attachToParentSurface"); + attachToParentSurface(builder); + mLeash = builder.build(); + b.setParent(mLeash); + } + + /** Creates the layout for compat controls. */ + void createLayout(boolean show) { + if (!show || mCompatUILayout != null) { + // Wait until compat controls should be visible. + return; + } + + initCompatUi(); + updateSurfacePosition(); + + mCallback.onSizeCompatRestartButtonAppeared(mTaskId); + } + + /** Called when compat info changed. */ + void updateCompatInfo(Configuration taskConfig, + ShellTaskOrganizer.TaskListener taskListener, boolean show) { + final Configuration prevTaskConfig = mTaskConfig; + final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener; + mTaskConfig = taskConfig; + mTaskListener = taskListener; + + // Update configuration. + mContext = mContext.createConfigurationContext(taskConfig); + setConfiguration(taskConfig); + + if (mCompatUILayout == null || prevTaskListener != taskListener) { + // TaskListener changed, recreate the layout for new surface parent. + release(); + createLayout(show); + return; + } + + if (!taskConfig.windowConfiguration.getBounds() + .equals(prevTaskConfig.windowConfiguration.getBounds())) { + // Reposition the UI surfaces. + updateSurfacePosition(); + } + + if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) { + // Update layout for RTL. + mCompatUILayout.setLayoutDirection(taskConfig.getLayoutDirection()); + updateSurfacePosition(); + } + } + + /** Called when the visibility of the UI should change. */ + void updateVisibility(boolean show) { + if (mCompatUILayout == null) { + // Layout may not have been created because it was hidden previously. + createLayout(show); + return; + } + + // Hide compat UIs when IME is showing. + final int newVisibility = show ? View.VISIBLE : View.GONE; + if (mCompatUILayout.getVisibility() != newVisibility) { + mCompatUILayout.setVisibility(newVisibility); + } + } + + /** Called when display layout changed. */ + void updateDisplayLayout(DisplayLayout displayLayout) { + final Rect prevStableBounds = mStableBounds; + final Rect curStableBounds = new Rect(); + displayLayout.getStableBounds(curStableBounds); + mDisplayLayout = displayLayout; + if (!prevStableBounds.equals(curStableBounds)) { + // Stable bounds changed, update UI surface positions. + updateSurfacePosition(); + mStableBounds.set(curStableBounds); + } + } + + /** Called when it is ready to be placed compat UI surface. */ + void attachToParentSurface(SurfaceControl.Builder b) { + mTaskListener.attachChildSurfaceToTask(mTaskId, b); + } + + /** Called when the restart button is clicked. */ + void onRestartButtonClicked() { + mCallback.onSizeCompatRestartButtonClicked(mTaskId); + } + + /** Called when the restart button is long clicked. */ + void onRestartButtonLongClicked() { + if (mCompatUILayout == null) { + return; + } + mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true); + } + + int getDisplayId() { + return mDisplayId; + } + + int getTaskId() { + return mTaskId; + } + + /** Releases the surface control and tears down the view hierarchy. */ + void release() { + mCompatUILayout = null; + + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + + if (mLeash != null) { + final SurfaceControl leash = mLeash; + mSyncQueue.runInSync(t -> t.remove(leash)); + mLeash = null; + } + } + + void relayout() { + mViewHost.relayout(getWindowLayoutParams()); + updateSurfacePosition(); + } + + @VisibleForTesting + void updateSurfacePosition() { + if (mCompatUILayout == null || mLeash == null) { + return; + } + + // Use stable bounds to prevent controls from overlapping with system bars. + final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); + final Rect stableBounds = new Rect(); + mDisplayLayout.getStableBounds(stableBounds); + stableBounds.intersect(taskBounds); + + // Position of the button in the container coordinate. + final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? stableBounds.left - taskBounds.left + : stableBounds.right - taskBounds.left - mCompatUILayout.getMeasuredWidth(); + final int positionY = stableBounds.bottom - taskBounds.top + - mCompatUILayout.getMeasuredHeight(); + + updateSurfacePosition(positionX, positionY); + } + + private int getLayoutDirection() { + return mContext.getResources().getConfiguration().getLayoutDirection(); + } + + private void updateSurfacePosition(int positionX, int positionY) { + mSyncQueue.runInSync(t -> { + if (mLeash == null || !mLeash.isValid()) { + Log.w(TAG, "The leash has been released."); + return; + } + t.setPosition(mLeash, positionX, positionY); + // The compat UI should be the topmost child of the Task in case there can be more + // than one children. + t.setLayer(mLeash, Integer.MAX_VALUE); + }); + } + + /** Inflates {@link CompatUILayout} on to the root surface. */ + private void initCompatUi() { + if (mViewHost != null) { + throw new IllegalStateException( + "A UI has already been created with this window manager."); + } + + // Construction extracted into the separate methods to allow injection for tests. + mViewHost = createSurfaceViewHost(); + mCompatUILayout = inflateCompatUILayout(); + mCompatUILayout.inject(this); + + mCompatUILayout.setSizeCompatHintVisibility(mShouldShowHint); + + mViewHost.setView(mCompatUILayout, getWindowLayoutParams()); + + // Only show by default for the first time. + mShouldShowHint = false; + } + + @VisibleForTesting + CompatUILayout inflateCompatUILayout() { + return (CompatUILayout) LayoutInflater.from(mContext) + .inflate(R.layout.compat_ui_layout, null); + } + + @VisibleForTesting + SurfaceControlViewHost createSurfaceViewHost() { + return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + } + + /** Gets the layout params. */ + private WindowManager.LayoutParams getWindowLayoutParams() { + // Measure how big the hint is since its size depends on the text size. + mCompatUILayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( + // Cannot be wrap_content as this determines the actual window size + mCompatUILayout.getMeasuredWidth(), mCompatUILayout.getMeasuredHeight(), + TYPE_APPLICATION_OVERLAY, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT); + winParams.token = new Binder(); + winParams.setTitle(CompatUILayout.class.getSimpleName() + mTaskId); + winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + return winParams; + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 7bc8d23d81de..6d158d591011 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -55,6 +55,8 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.compatui.CompatUI; +import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDrop; @@ -76,8 +78,6 @@ import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.sizecompatui.SizeCompatUI; -import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; @@ -174,25 +174,25 @@ public abstract class WMShellBaseModule { @Provides static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, Context context, - SizeCompatUIController sizeCompatUI, + CompatUIController compatUI, Optional<RecentTasksController> recentTasksOptional ) { - return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI, recentTasksOptional); + return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional); } @WMSingleton @Provides - static SizeCompatUI provideSizeCompatUI(SizeCompatUIController sizeCompatUIController) { - return sizeCompatUIController.asSizeCompatUI(); + static CompatUI provideCompatUI(CompatUIController compatUIController) { + return compatUIController.asCompatUI(); } @WMSingleton @Provides - static SizeCompatUIController provideSizeCompatUIController(Context context, + static CompatUIController provideCompatUIController(Context context, DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor) { - return new SizeCompatUIController(context, displayController, displayInsetsController, + return new CompatUIController(context, displayController, displayInsetsController, imeController, syncQueue, mainExecutor); } 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 854fc60e15e8..f0b2716f05d8 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 @@ -778,8 +778,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } @Override - public boolean supportSizeCompatUI() { - // PIP doesn't support size compat. + public boolean supportCompatUI() { + // PIP doesn't support compat. return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java deleted file mode 100644 index ff6f913207f6..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.sizecompatui; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -import androidx.annotation.Nullable; - -import com.android.wm.shell.R; - -/** Popup to show the hint about the {@link SizeCompatRestartButton}. */ -public class SizeCompatHintPopup extends FrameLayout implements View.OnClickListener { - - private SizeCompatUILayout mLayout; - - public SizeCompatHintPopup(Context context) { - super(context); - } - - public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public SizeCompatHintPopup(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - void inject(SizeCompatUILayout layout) { - mLayout = layout; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - final LinearLayout hintPopup = findViewById(R.id.size_compat_hint_popup); - hintPopup.setOnClickListener(this); - } - - @Override - public void onClick(View v) { - mLayout.dismissHint(); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java deleted file mode 100644 index d75fe5173c5f..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.sizecompatui; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageButton; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.wm.shell.R; - -/** Button to restart the size compat activity. */ -public class SizeCompatRestartButton extends FrameLayout implements View.OnClickListener, - View.OnLongClickListener { - - private SizeCompatUILayout mLayout; - - public SizeCompatRestartButton(@NonNull Context context) { - super(context); - } - - public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - void inject(SizeCompatUILayout layout) { - mLayout = layout; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - final ImageButton restartButton = findViewById(R.id.size_compat_restart_button); - restartButton.setOnClickListener(this); - restartButton.setOnLongClickListener(this); - } - - @Override - public void onClick(View v) { - mLayout.onRestartButtonClicked(); - } - - @Override - public boolean onLongClick(View v) { - mLayout.onRestartButtonLongClicked(); - return true; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java deleted file mode 100644 index c35b89af6c1b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.sizecompatui; - -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.os.Binder; -import android.util.Log; -import android.view.SurfaceControl; -import android.view.View; -import android.view.WindowManager; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.wm.shell.R; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.SyncTransactionQueue; - -/** - * Records and handles layout of size compat UI on a task with size compat activity. Helps to - * calculate proper bounds when configuration or UI position changes. - */ -class SizeCompatUILayout { - private static final String TAG = "SizeCompatUILayout"; - - final SyncTransactionQueue mSyncQueue; - private final SizeCompatUIController.SizeCompatUICallback mCallback; - private Context mContext; - private Configuration mTaskConfig; - private final int mDisplayId; - private final int mTaskId; - private ShellTaskOrganizer.TaskListener mTaskListener; - private DisplayLayout mDisplayLayout; - private final Rect mStableBounds; - private final int mButtonWidth; - private final int mButtonHeight; - private final int mPopupOffsetX; - private final int mPopupOffsetY; - - @VisibleForTesting - final SizeCompatUIWindowManager mButtonWindowManager; - @VisibleForTesting - @Nullable - SizeCompatUIWindowManager mHintWindowManager; - @VisibleForTesting - @Nullable - SizeCompatRestartButton mButton; - @VisibleForTesting - @Nullable - SizeCompatHintPopup mHint; - @VisibleForTesting - boolean mShouldShowHint; - - SizeCompatUILayout(SyncTransactionQueue syncQueue, - SizeCompatUIController.SizeCompatUICallback callback, Context context, - Configuration taskConfig, int taskId, ShellTaskOrganizer.TaskListener taskListener, - DisplayLayout displayLayout, boolean hasShownHint) { - mSyncQueue = syncQueue; - mCallback = callback; - mContext = context.createConfigurationContext(taskConfig); - mTaskConfig = taskConfig; - mDisplayId = mContext.getDisplayId(); - mTaskId = taskId; - mTaskListener = taskListener; - mDisplayLayout = displayLayout; - mShouldShowHint = !hasShownHint; - mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this); - - mStableBounds = new Rect(); - mDisplayLayout.getStableBounds(mStableBounds); - - final Resources resources = mContext.getResources(); - mButtonWidth = resources.getDimensionPixelSize(R.dimen.size_compat_button_width); - mButtonHeight = resources.getDimensionPixelSize(R.dimen.size_compat_button_height); - mPopupOffsetX = (mButtonWidth / 2) - resources.getDimensionPixelSize( - R.dimen.size_compat_hint_corner_radius) - (resources.getDimensionPixelSize( - R.dimen.size_compat_hint_point_width) / 2); - mPopupOffsetY = mButtonHeight; - } - - /** Creates the activity restart button window. */ - void createSizeCompatButton(boolean show) { - if (!show || mButton != null) { - // Wait until button should be visible. - return; - } - mButton = mButtonWindowManager.createSizeCompatButton(); - updateButtonSurfacePosition(); - - if (mShouldShowHint) { - // Only show by default for the first time. - mShouldShowHint = false; - createSizeCompatHint(); - } - - mCallback.onSizeCompatRestartButtonAppeared(mTaskId); - } - - /** Creates the restart button hint window. */ - private void createSizeCompatHint() { - if (mHint != null) { - // Hint already shown. - return; - } - mHintWindowManager = createHintWindowManager(); - mHint = mHintWindowManager.createSizeCompatHint(); - updateHintSurfacePosition(); - } - - @VisibleForTesting - SizeCompatUIWindowManager createHintWindowManager() { - return new SizeCompatUIWindowManager(mContext, mTaskConfig, this); - } - - /** Dismisses the hint window. */ - void dismissHint() { - mHint = null; - if (mHintWindowManager != null) { - mHintWindowManager.release(); - mHintWindowManager = null; - } - } - - /** Releases the UI windows. */ - void release() { - dismissHint(); - mButton = null; - mButtonWindowManager.release(); - } - - /** Called when size compat info changed. */ - void updateSizeCompatInfo(Configuration taskConfig, - ShellTaskOrganizer.TaskListener taskListener, boolean show) { - final Configuration prevTaskConfig = mTaskConfig; - final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener; - mTaskConfig = taskConfig; - mTaskListener = taskListener; - - // Update configuration. - mContext = mContext.createConfigurationContext(taskConfig); - mButtonWindowManager.setConfiguration(taskConfig); - if (mHintWindowManager != null) { - mHintWindowManager.setConfiguration(taskConfig); - } - - if (mButton == null || prevTaskListener != taskListener) { - // TaskListener changed, recreate the button for new surface parent. - release(); - createSizeCompatButton(show); - return; - } - - if (!taskConfig.windowConfiguration.getBounds() - .equals(prevTaskConfig.windowConfiguration.getBounds())) { - // Reposition the UI surfaces. - updateAllSurfacePositions(); - } - - if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) { - // Update layout for RTL. - mButton.setLayoutDirection(taskConfig.getLayoutDirection()); - updateButtonSurfacePosition(); - if (mHint != null) { - mHint.setLayoutDirection(taskConfig.getLayoutDirection()); - updateHintSurfacePosition(); - } - } - } - - /** Called when display layout changed. */ - void updateDisplayLayout(DisplayLayout displayLayout) { - final Rect prevStableBounds = mStableBounds; - final Rect curStableBounds = new Rect(); - displayLayout.getStableBounds(curStableBounds); - mDisplayLayout = displayLayout; - if (!prevStableBounds.equals(curStableBounds)) { - // Stable bounds changed, update UI surface positions. - updateAllSurfacePositions(); - mStableBounds.set(curStableBounds); - } - } - - /** Called when the visibility of the UI should change. */ - void updateVisibility(boolean show) { - if (mButton == null) { - // Button may not have been created because it was hidden previously. - createSizeCompatButton(show); - return; - } - - // Hide size compat UIs when IME is showing. - final int newVisibility = show ? View.VISIBLE : View.GONE; - if (mButton.getVisibility() != newVisibility) { - mButton.setVisibility(newVisibility); - } - if (mHint != null && mHint.getVisibility() != newVisibility) { - mHint.setVisibility(newVisibility); - } - } - - /** Gets the layout params for restart button. */ - WindowManager.LayoutParams getButtonWindowLayoutParams() { - final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( - // Cannot be wrap_content as this determines the actual window size - mButtonWidth, mButtonHeight, - TYPE_APPLICATION_OVERLAY, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, - PixelFormat.TRANSLUCENT); - winParams.token = new Binder(); - winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + getTaskId()); - winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - return winParams; - } - - /** Gets the layout params for hint popup. */ - WindowManager.LayoutParams getHintWindowLayoutParams(SizeCompatHintPopup hint) { - final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( - // Cannot be wrap_content as this determines the actual window size - hint.getMeasuredWidth(), hint.getMeasuredHeight(), - TYPE_APPLICATION_OVERLAY, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, - PixelFormat.TRANSLUCENT); - winParams.token = new Binder(); - winParams.setTitle(SizeCompatHintPopup.class.getSimpleName() + getTaskId()); - winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - winParams.windowAnimations = android.R.style.Animation_InputMethod; - return winParams; - } - - /** Called when it is ready to be placed size compat UI surface. */ - void attachToParentSurface(SurfaceControl.Builder b) { - mTaskListener.attachChildSurfaceToTask(mTaskId, b); - } - - /** Called when the restart button is clicked. */ - void onRestartButtonClicked() { - mCallback.onSizeCompatRestartButtonClicked(mTaskId); - } - - /** Called when the restart button is long clicked. */ - void onRestartButtonLongClicked() { - createSizeCompatHint(); - } - - private void updateAllSurfacePositions() { - updateButtonSurfacePosition(); - updateHintSurfacePosition(); - } - - @VisibleForTesting - void updateButtonSurfacePosition() { - if (mButton == null || mButtonWindowManager.getSurfaceControl() == null) { - return; - } - final SurfaceControl leash = mButtonWindowManager.getSurfaceControl(); - - // Use stable bounds to prevent the button from overlapping with system bars. - final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); - final Rect stableBounds = new Rect(); - mDisplayLayout.getStableBounds(stableBounds); - stableBounds.intersect(taskBounds); - - // Position of the button in the container coordinate. - final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL - ? stableBounds.left - taskBounds.left - : stableBounds.right - taskBounds.left - mButtonWidth; - final int positionY = stableBounds.bottom - taskBounds.top - mButtonHeight; - - updateSurfacePosition(leash, positionX, positionY); - } - - @VisibleForTesting - void updateHintSurfacePosition() { - if (mHint == null || mHintWindowManager == null - || mHintWindowManager.getSurfaceControl() == null) { - return; - } - final SurfaceControl leash = mHintWindowManager.getSurfaceControl(); - - // Use stable bounds to prevent the hint from overlapping with system bars. - final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); - final Rect stableBounds = new Rect(); - mDisplayLayout.getStableBounds(stableBounds); - stableBounds.intersect(taskBounds); - - // Position of the hint in the container coordinate. - final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL - ? stableBounds.left - taskBounds.left + mPopupOffsetX - : stableBounds.right - taskBounds.left - mPopupOffsetX - mHint.getMeasuredWidth(); - final int positionY = - stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight(); - - updateSurfacePosition(leash, positionX, positionY); - } - - private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) { - mSyncQueue.runInSync(t -> { - if (!leash.isValid()) { - Log.w(TAG, "The leash has been released."); - return; - } - t.setPosition(leash, positionX, positionY); - // The size compat UI should be the topmost child of the Task in case there can be more - // than one children. - t.setLayer(leash, Integer.MAX_VALUE); - }); - } - - int getDisplayId() { - return mDisplayId; - } - - int getTaskId() { - return mTaskId; - } - - private int getLayoutDirection() { - return mContext.getResources().getConfiguration().getLayoutDirection(); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java deleted file mode 100644 index 82f69c3e2985..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.sizecompatui; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.Configuration; -import android.view.IWindow; -import android.view.LayoutInflater; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.SurfaceSession; -import android.view.View; -import android.view.WindowlessWindowManager; - -import com.android.wm.shell.R; - -/** - * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton} or - * {@link SizeCompatHintPopup}. - */ -class SizeCompatUIWindowManager extends WindowlessWindowManager { - - private Context mContext; - private final SizeCompatUILayout mLayout; - - @Nullable - private SurfaceControlViewHost mViewHost; - @Nullable - private SurfaceControl mLeash; - - SizeCompatUIWindowManager(Context context, Configuration config, SizeCompatUILayout layout) { - super(config, null /* rootSurface */, null /* hostInputToken */); - mContext = context; - mLayout = layout; - } - - @Override - public void setConfiguration(Configuration configuration) { - super.setConfiguration(configuration); - mContext = mContext.createConfigurationContext(configuration); - } - - @Override - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { - // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. - final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) - .setContainerLayer() - .setName("SizeCompatUILeash") - .setHidden(false) - .setCallsite("SizeCompatUIWindowManager#attachToParentSurface"); - mLayout.attachToParentSurface(builder); - mLeash = builder.build(); - b.setParent(mLeash); - } - - /** Inflates {@link SizeCompatRestartButton} on to the root surface. */ - SizeCompatRestartButton createSizeCompatButton() { - if (mViewHost != null) { - throw new IllegalStateException( - "A UI has already been created with this window manager."); - } - - mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); - - final SizeCompatRestartButton button = (SizeCompatRestartButton) - LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null); - button.inject(mLayout); - mViewHost.setView(button, mLayout.getButtonWindowLayoutParams()); - return button; - } - - /** Inflates {@link SizeCompatHintPopup} on to the root surface. */ - SizeCompatHintPopup createSizeCompatHint() { - if (mViewHost != null) { - throw new IllegalStateException( - "A UI has already been created with this window manager."); - } - - mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); - - final SizeCompatHintPopup hint = (SizeCompatHintPopup) - LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null); - // Measure how big the hint is. - hint.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - hint.inject(mLayout); - mViewHost.setView(hint, mLayout.getHintWindowLayoutParams(hint)); - return hint; - } - - /** Releases the surface control and tears down the view hierarchy. */ - void release() { - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - - if (mLeash != null) { - final SurfaceControl leash = mLeash; - mLayout.mSyncQueue.runInSync(t -> t.remove(leash)); - mLeash = null; - } - } - - /** - * Gets {@link SurfaceControl} of the surface holding size compat UI view. @return {@code null} - * if not feasible. - */ - @Nullable - SurfaceControl getSurfaceControl() { - return mLeash; - } -} 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 ed5de0d698f3..cdaa54c7266d 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 @@ -342,12 +342,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, sideOptions = sideOptions != null ? sideOptions : new Bundle(); setSideStagePosition(sidePosition, wct); + mSplitLayout.setDivideRatio(splitRatio); // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); mSideStage.setBounds(getSideStageBounds(), wct); - mSplitLayout.setDivideRatio(splitRatio); // Make sure the launch options will put tasks in the corresponding split roots addActivityOptions(mainOptions, mMainStage); addActivityOptions(sideOptions, mSideStage); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 6643ca176280..4ecc0b685626 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -380,9 +380,7 @@ public class TaskSnapshotWindow { } private void drawSizeMatchSnapshot() { - GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( - mSnapshot.getHardwareBuffer()); - mTransaction.setBuffer(mSurfaceControl, graphicBuffer) + mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer()) .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace()) .apply(); } @@ -428,20 +426,20 @@ public class TaskSnapshotWindow { // Scale the mismatch dimensions to fill the task bounds mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL); mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9); - GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( - mSnapshot.getHardwareBuffer()); mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); - mTransaction.setBuffer(childSurfaceControl, graphicBuffer); + mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); if (aspectRatioMismatch) { GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(), PixelFormat.RGBA_8888, GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER | GraphicBuffer.USAGE_SW_WRITE_RARELY); + // TODO: Support this on HardwareBuffer final Canvas c = background.lockCanvas(); drawBackgroundAndBars(c, frame); background.unlockCanvasAndPost(c); - mTransaction.setBuffer(mSurfaceControl, background); + mTransaction.setBuffer(mSurfaceControl, + HardwareBuffer.createFromGraphicBuffer(background)); } mTransaction.apply(); childSurfaceControl.release(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 1fcbf14fb732..a3b98a8fc880 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -56,7 +56,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.sizecompatui.SizeCompatUIController; +import com.android.wm.shell.compatui.CompatUIController; import org.junit.Before; import org.junit.Test; @@ -82,7 +82,7 @@ public class ShellTaskOrganizerTests { @Mock private Context mContext; @Mock - private SizeCompatUIController mSizeCompatUI; + private CompatUIController mCompatUI; ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); @@ -132,7 +132,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mSizeCompatUI, Optional.empty())); + mCompatUI, Optional.empty())); } @Test @@ -334,34 +334,34 @@ public class ShellTaskOrganizerTests { mOrganizer.onTaskAppeared(taskInfo1, null); // sizeCompatActivity is null if top activity is not in size compat. - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, null /* taskConfig */, null /* taskListener */); // sizeCompatActivity is non-null if top activity is in size compat. - clearInvocations(mSizeCompatUI); + clearInvocations(mCompatUI); final RunningTaskInfo taskInfo2 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo2.displayId = taskInfo1.displayId; taskInfo2.topActivityInSizeCompat = true; taskInfo2.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo2); - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, taskInfo1.configuration, taskListener); // Not show size compat UI if task is not visible. - clearInvocations(mSizeCompatUI); + clearInvocations(mCompatUI); final RunningTaskInfo taskInfo3 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo3.displayId = taskInfo1.displayId; taskInfo3.topActivityInSizeCompat = true; taskInfo3.isVisible = false; mOrganizer.onTaskInfoChanged(taskInfo3); - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, null /* taskConfig */, null /* taskListener */); - clearInvocations(mSizeCompatUI); + clearInvocations(mCompatUI); mOrganizer.onTaskVanished(taskInfo1); - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, null /* taskConfig */, null /* taskListener */); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index bc701d0c70bc..8bc1223cfd64 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -39,6 +39,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import android.util.Pair; import android.view.WindowManager; @@ -913,6 +914,31 @@ public class BubbleDataTest extends ShellTestCase { assertSelectionChangedTo(mBubbleA2); } + /** + * - have a maxed out bubble stack & all of the bubbles have been recently accessed + * - bubble a notification that was posted before any of those bubbles were accessed + * => that bubble should be added + * + */ + @Test + public void test_addOldNotifWithNewerBubbles() { + sendUpdatedEntryAtTime(mEntryA1, 2000); + sendUpdatedEntryAtTime(mEntryA2, 3000); + sendUpdatedEntryAtTime(mEntryA3, 4000); + sendUpdatedEntryAtTime(mEntryB1, 5000); + sendUpdatedEntryAtTime(mEntryB2, 6000); + + mBubbleData.setListener(mListener); + sendUpdatedEntryAtTime(mEntryB3, 1000 /* postTime */, 7000 /* currentTime */); + verifyUpdateReceived(); + + // B3 is in the stack + assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleB3.getKey())).isNotNull(); + // A1 is the oldest so it's in the overflow + assertThat(mBubbleData.getOverflowBubbleWithKey(mEntryA1.getKey())).isNotNull(); + assertOrderChangedTo(mBubbleB3, mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); @@ -1014,6 +1040,12 @@ public class BubbleDataTest extends ShellTestCase { } private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) { + setCurrentTime(postTime); + sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */); + } + + private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, long currentTime) { + setCurrentTime(currentTime); sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 453050fcfab4..83d5f04b7cdb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -101,14 +102,21 @@ public class SplitLayoutTests extends ShellTestCase { @Test public void testSetDividePosition() { - mSplitLayout.setDividePosition(anyInt()); + mSplitLayout.setDividePosition(100, false /* applyLayoutChange */); + assertThat(mSplitLayout.getDividePosition()).isEqualTo(100); + verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class)); + + mSplitLayout.setDividePosition(200, true /* applyLayoutChange */); + assertThat(mSplitLayout.getDividePosition()).isEqualTo(200); verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); } @Test public void testSetDivideRatio() { + mSplitLayout.setDividePosition(200, false /* applyLayoutChange */); mSplitLayout.setDivideRatio(0.5f); - verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); + assertThat(mSplitLayout.getDividePosition()).isEqualTo( + mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 877b19223bf6..f622edb7f134 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.sizecompatui; +package com.android.wm.shell.compatui; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; @@ -56,18 +56,18 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Tests for {@link SizeCompatUIController}. + * Tests for {@link CompatUIController}. * * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatUIControllerTest + * atest WMShellUnitTests:CompatUIControllerTest */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class SizeCompatUIControllerTest extends ShellTestCase { +public class CompatUIControllerTest extends ShellTestCase { private static final int DISPLAY_ID = 0; private static final int TASK_ID = 12; - private SizeCompatUIController mController; + private CompatUIController mController; private @Mock DisplayController mMockDisplayController; private @Mock DisplayInsetsController mMockDisplayInsetsController; private @Mock DisplayLayout mMockDisplayLayout; @@ -75,7 +75,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener; private @Mock SyncTransactionQueue mMockSyncQueue; private @Mock ShellExecutor mMockExecutor; - private @Mock SizeCompatUILayout mMockLayout; + private @Mock CompatUIWindowManager mMockLayout; @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; @@ -87,10 +87,10 @@ public class SizeCompatUIControllerTest extends ShellTestCase { doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt()); doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId(); doReturn(TASK_ID).when(mMockLayout).getTaskId(); - mController = new SizeCompatUIController(mContext, mMockDisplayController, + mController = new CompatUIController(mContext, mMockDisplayController, mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) { @Override - SizeCompatUILayout createLayout(Context context, int displayId, int taskId, + CompatUIWindowManager createLayout(Context context, int displayId, int taskId, Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) { return mMockLayout; } @@ -105,24 +105,24 @@ public class SizeCompatUIControllerTest extends ShellTestCase { } @Test - public void testOnSizeCompatInfoChanged() { + public void testOnCompatInfoChanged() { final Configuration taskConfig = new Configuration(); // Verify that the restart button is added with non-null size compat info. - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig), eq(mMockTaskListener)); // Verify that the restart button is updated with non-null new size compat info. final Configuration newTaskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener); - verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener, + verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener, true /* show */); // Verify that the restart button is removed with null size compat info. - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener); verify(mMockLayout).release(); } @@ -140,7 +140,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { public void testOnDisplayRemoved() { mController.onDisplayAdded(DISPLAY_ID); final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); mController.onDisplayRemoved(DISPLAY_ID + 1); @@ -158,7 +158,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { @Test public void testOnDisplayConfigurationChanged() { final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); final Configuration newTaskConfig = new Configuration(); @@ -175,7 +175,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { public void testInsetsChanged() { mController.onDisplayAdded(DISPLAY_ID); final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); InsetsState insetsState = new InsetsState(); InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); @@ -197,7 +197,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { @Test public void testChangeButtonVisibilityOnImeShowHide() { final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); // Verify that the restart button is hidden after IME is showing. mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); @@ -205,9 +205,9 @@ public class SizeCompatUIControllerTest extends ShellTestCase { verify(mMockLayout).updateVisibility(false); // Verify button remains hidden while IME is showing. - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); - verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener, + verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener, false /* show */); // Verify button is shown after IME is hidden. @@ -219,7 +219,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { @Test public void testChangeButtonVisibilityOnKeyguardOccludedChanged() { final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); // Verify that the restart button is hidden after keyguard becomes occluded. mController.onKeyguardOccludedChanged(true); @@ -227,9 +227,9 @@ public class SizeCompatUIControllerTest extends ShellTestCase { verify(mMockLayout).updateVisibility(false); // Verify button remains hidden while keyguard is occluded. - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); - verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener, + verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener, false /* show */); // Verify button is shown after keyguard becomes not occluded. @@ -241,7 +241,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { @Test public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() { final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); mController.onKeyguardOccludedChanged(true); @@ -264,7 +264,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { @Test public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() { final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); mController.onKeyguardOccludedChanged(true); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index a20a5e9e8d91..2c3987bc358d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -14,17 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.sizecompatui; +package com.android.wm.shell.compatui; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; +import android.view.SurfaceControlViewHost; import android.widget.ImageButton; +import android.widget.LinearLayout; import androidx.test.filters.SmallTest; @@ -41,55 +44,68 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Tests for {@link SizeCompatRestartButton}. + * Tests for {@link CompatUILayout}. * * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatRestartButtonTest + * atest WMShellUnitTests:CompatUILayoutTest */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class SizeCompatRestartButtonTest extends ShellTestCase { +public class CompatUILayoutTest extends ShellTestCase { private static final int TASK_ID = 1; @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private SizeCompatUIController.SizeCompatUICallback mCallback; + @Mock private CompatUIController.CompatUICallback mCallback; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; - @Mock private DisplayLayout mDisplayLayout; + @Mock private SurfaceControlViewHost mViewHost; - private SizeCompatUILayout mLayout; - private SizeCompatRestartButton mButton; + private CompatUIWindowManager mWindowManager; + private CompatUILayout mCompatUILayout; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext, - new Configuration(), TASK_ID, mTaskListener, mDisplayLayout, + mWindowManager = new CompatUIWindowManager(mContext, new Configuration(), + mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(), false /* hasShownHint */); - mButton = (SizeCompatRestartButton) - LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null); - mButton.inject(mLayout); - spyOn(mLayout); + mCompatUILayout = (CompatUILayout) + LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null); + mCompatUILayout.inject(mWindowManager); + + spyOn(mWindowManager); + spyOn(mCompatUILayout); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); } @Test - public void testOnClick() { - final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button); + public void testOnClickForRestartButton() { + final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button); button.performClick(); - verify(mLayout).onRestartButtonClicked(); + verify(mWindowManager).onRestartButtonClicked(); + doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout(); verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); } @Test - public void testOnLongClick() { - doNothing().when(mLayout).onRestartButtonLongClicked(); + public void testOnLongClickForRestartButton() { + doNothing().when(mWindowManager).onRestartButtonLongClicked(); - final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button); + final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button); button.performLongClick(); - verify(mLayout).onRestartButtonLongClicked(); + verify(mWindowManager).onRestartButtonLongClicked(); + } + + @Test + public void testOnClickForSizeCompatHint() { + mWindowManager.createLayout(true /* show */); + final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint); + sizeCompatHint.performClick(); + + verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java new file mode 100644 index 000000000000..d5dcf2e11a46 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui; + +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.view.DisplayInfo; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link CompatUIWindowManager}. + * + * Build/Install/Run: + * atest WMShellUnitTests:CompatUIWindowManagerTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class CompatUIWindowManagerTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private CompatUILayout mCompatUILayout; + @Mock private SurfaceControlViewHost mViewHost; + private Configuration mTaskConfig; + + private CompatUIWindowManager mWindowManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTaskConfig = new Configuration(); + + mWindowManager = new CompatUIWindowManager(mContext, new Configuration(), + mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(), + false /* hasShownHint */); + + spyOn(mWindowManager); + doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout(); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + } + + @Test + public void testCreateSizeCompatButton() { + // Not create layout if show is false. + mWindowManager.createLayout(false /* show */); + + verify(mWindowManager, never()).inflateCompatUILayout(); + + // Not create hint popup. + mWindowManager.mShouldShowHint = false; + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager).inflateCompatUILayout(); + verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */); + + // Create hint popup. + mWindowManager.release(); + mWindowManager.mShouldShowHint = true; + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager, times(2)).inflateCompatUILayout(); + assertNotNull(mCompatUILayout); + verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */); + assertFalse(mWindowManager.mShouldShowHint); + } + + @Test + public void testRelease() { + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager).inflateCompatUILayout(); + + mWindowManager.release(); + + verify(mViewHost).release(); + } + + @Test + public void testUpdateCompatInfo() { + mWindowManager.createLayout(true /* show */); + + // No diff + clearInvocations(mWindowManager); + mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */); + + verify(mWindowManager, never()).updateSurfacePosition(); + verify(mWindowManager, never()).release(); + verify(mWindowManager, never()).createLayout(anyBoolean()); + + // Change task listener, recreate button. + clearInvocations(mWindowManager); + final ShellTaskOrganizer.TaskListener newTaskListener = mock( + ShellTaskOrganizer.TaskListener.class); + mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, + true /* show */); + + verify(mWindowManager).release(); + verify(mWindowManager).createLayout(anyBoolean()); + + // Change task bounds, update position. + clearInvocations(mWindowManager); + final Configuration newTaskConfiguration = new Configuration(); + newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000)); + mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener, + true /* show */); + + verify(mWindowManager).updateSurfacePosition(); + } + + @Test + public void testUpdateDisplayLayout() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout1); + verify(mWindowManager).updateSurfacePosition(); + + // No update if the display bounds is the same. + clearInvocations(mWindowManager); + final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + mWindowManager.updateDisplayLayout(displayLayout2); + verify(mWindowManager, never()).updateSurfacePosition(); + } + + @Test + public void testUpdateDisplayLayoutInsets() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + + // Update if the insets change on the existing display layout + clearInvocations(mWindowManager); + InsetsState insetsState = new InsetsState(); + InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + insetsSource.setFrame(0, 0, 1000, 1000); + insetsState.addSource(insetsSource); + displayLayout.setInsets(mContext.getResources(), insetsState); + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + } + + @Test + public void testUpdateVisibility() { + // Create button if it is not created. + mWindowManager.mCompatUILayout = null; + mWindowManager.updateVisibility(true /* show */); + + verify(mWindowManager).createLayout(true /* show */); + + // Hide button. + clearInvocations(mWindowManager); + doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility(); + mWindowManager.updateVisibility(false /* show */); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mCompatUILayout).setVisibility(View.GONE); + + // Show button. + doReturn(View.GONE).when(mCompatUILayout).getVisibility(); + mWindowManager.updateVisibility(true /* show */); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mCompatUILayout).setVisibility(View.VISIBLE); + } + + @Test + public void testAttachToParentSurface() { + final SurfaceControl.Builder b = new SurfaceControl.Builder(); + mWindowManager.attachToParentSurface(b); + + verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b); + } + + @Test + public void testOnRestartButtonClicked() { + mWindowManager.onRestartButtonClicked(); + + verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); + } + + @Test + public void testOnRestartButtonLongClicked_showHint() { + // Not create hint popup. + mWindowManager.mShouldShowHint = false; + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager).inflateCompatUILayout(); + verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */); + + mWindowManager.onRestartButtonLongClicked(); + + verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java deleted file mode 100644 index 3a14a336190d..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.sizecompatui; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.verify; - -import android.content.res.Configuration; -import android.testing.AndroidTestingRunner; -import android.view.LayoutInflater; -import android.widget.LinearLayout; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.R; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link SizeCompatHintPopup}. - * - * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatHintPopupTest - */ -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class SizeCompatHintPopupTest extends ShellTestCase { - - @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private SizeCompatUIController.SizeCompatUICallback mCallback; - @Mock private ShellTaskOrganizer.TaskListener mTaskListener; - @Mock private DisplayLayout mDisplayLayout; - - private SizeCompatUILayout mLayout; - private SizeCompatHintPopup mHint; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - final int taskId = 1; - mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext, - new Configuration(), taskId, mTaskListener, mDisplayLayout, - false /* hasShownHint */); - mHint = (SizeCompatHintPopup) - LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null); - mHint.inject(mLayout); - - spyOn(mLayout); - } - - @Test - public void testOnClick() { - doNothing().when(mLayout).dismissHint(); - - final LinearLayout hintPopup = mHint.findViewById(R.id.size_compat_hint_popup); - hintPopup.performClick(); - - verify(mLayout).dismissHint(); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java deleted file mode 100644 index eb9305b2e995..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.sizecompatui; - -import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.res.Configuration; -import android.graphics.Rect; -import android.testing.AndroidTestingRunner; -import android.view.DisplayInfo; -import android.view.InsetsSource; -import android.view.InsetsState; -import android.view.SurfaceControl; -import android.view.View; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link SizeCompatUILayout}. - * - * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatUILayoutTest - */ -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class SizeCompatUILayoutTest extends ShellTestCase { - - private static final int TASK_ID = 1; - - @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private SizeCompatUIController.SizeCompatUICallback mCallback; - @Mock private ShellTaskOrganizer.TaskListener mTaskListener; - @Mock private DisplayLayout mDisplayLayout; - @Mock private SizeCompatRestartButton mButton; - @Mock private SizeCompatHintPopup mHint; - private Configuration mTaskConfig; - - private SizeCompatUILayout mLayout; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mTaskConfig = new Configuration(); - - mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext, - new Configuration(), TASK_ID, mTaskListener, new DisplayLayout(), - false /* hasShownHint */); - - spyOn(mLayout); - spyOn(mLayout.mButtonWindowManager); - doReturn(mButton).when(mLayout.mButtonWindowManager).createSizeCompatButton(); - - final SizeCompatUIWindowManager hintWindowManager = mLayout.createHintWindowManager(); - spyOn(hintWindowManager); - doReturn(mHint).when(hintWindowManager).createSizeCompatHint(); - doReturn(hintWindowManager).when(mLayout).createHintWindowManager(); - } - - @Test - public void testCreateSizeCompatButton() { - // Not create button if show is false. - mLayout.createSizeCompatButton(false /* show */); - - verify(mLayout.mButtonWindowManager, never()).createSizeCompatButton(); - assertNull(mLayout.mButton); - assertNull(mLayout.mHintWindowManager); - assertNull(mLayout.mHint); - - // Not create hint popup. - mLayout.mShouldShowHint = false; - mLayout.createSizeCompatButton(true /* show */); - - verify(mLayout.mButtonWindowManager).createSizeCompatButton(); - assertNotNull(mLayout.mButton); - assertNull(mLayout.mHintWindowManager); - assertNull(mLayout.mHint); - - // Create hint popup. - mLayout.release(); - mLayout.mShouldShowHint = true; - mLayout.createSizeCompatButton(true /* show */); - - verify(mLayout.mButtonWindowManager, times(2)).createSizeCompatButton(); - assertNotNull(mLayout.mButton); - assertNotNull(mLayout.mHintWindowManager); - verify(mLayout.mHintWindowManager).createSizeCompatHint(); - assertNotNull(mLayout.mHint); - assertFalse(mLayout.mShouldShowHint); - } - - @Test - public void testRelease() { - mLayout.createSizeCompatButton(true /* show */); - final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager; - - mLayout.release(); - - assertNull(mLayout.mButton); - assertNull(mLayout.mHint); - verify(hintWindowManager).release(); - assertNull(mLayout.mHintWindowManager); - verify(mLayout.mButtonWindowManager).release(); - } - - @Test - public void testUpdateSizeCompatInfo() { - mLayout.createSizeCompatButton(true /* show */); - - // No diff - clearInvocations(mLayout); - mLayout.updateSizeCompatInfo(mTaskConfig, mTaskListener, true /* show */); - - verify(mLayout, never()).updateButtonSurfacePosition(); - verify(mLayout, never()).release(); - verify(mLayout, never()).createSizeCompatButton(anyBoolean()); - - // Change task listener, recreate button. - clearInvocations(mLayout); - final ShellTaskOrganizer.TaskListener newTaskListener = mock( - ShellTaskOrganizer.TaskListener.class); - mLayout.updateSizeCompatInfo(mTaskConfig, newTaskListener, - true /* show */); - - verify(mLayout).release(); - verify(mLayout).createSizeCompatButton(anyBoolean()); - - // Change task bounds, update position. - clearInvocations(mLayout); - final Configuration newTaskConfiguration = new Configuration(); - newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000)); - mLayout.updateSizeCompatInfo(newTaskConfiguration, newTaskListener, - true /* show */); - - verify(mLayout).updateButtonSurfacePosition(); - verify(mLayout).updateHintSurfacePosition(); - } - - @Test - public void testUpdateDisplayLayout() { - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = 1000; - displayInfo.logicalHeight = 2000; - final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, - mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); - - mLayout.updateDisplayLayout(displayLayout1); - verify(mLayout).updateButtonSurfacePosition(); - verify(mLayout).updateHintSurfacePosition(); - - // No update if the display bounds is the same. - clearInvocations(mLayout); - final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, - mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); - mLayout.updateDisplayLayout(displayLayout2); - verify(mLayout, never()).updateButtonSurfacePosition(); - verify(mLayout, never()).updateHintSurfacePosition(); - } - - @Test - public void testUpdateDisplayLayoutInsets() { - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = 1000; - displayInfo.logicalHeight = 2000; - final DisplayLayout displayLayout = new DisplayLayout(displayInfo, - mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false); - - mLayout.updateDisplayLayout(displayLayout); - verify(mLayout).updateButtonSurfacePosition(); - verify(mLayout).updateHintSurfacePosition(); - - // Update if the insets change on the existing display layout - clearInvocations(mLayout); - InsetsState insetsState = new InsetsState(); - InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); - insetsSource.setFrame(0, 0, 1000, 1000); - insetsState.addSource(insetsSource); - displayLayout.setInsets(mContext.getResources(), insetsState); - mLayout.updateDisplayLayout(displayLayout); - verify(mLayout).updateButtonSurfacePosition(); - verify(mLayout).updateHintSurfacePosition(); - } - - @Test - public void testUpdateVisibility() { - // Create button if it is not created. - mLayout.mButton = null; - mLayout.updateVisibility(true /* show */); - - verify(mLayout).createSizeCompatButton(true /* show */); - - // Hide button. - clearInvocations(mLayout); - doReturn(View.VISIBLE).when(mButton).getVisibility(); - mLayout.updateVisibility(false /* show */); - - verify(mLayout, never()).createSizeCompatButton(anyBoolean()); - verify(mButton).setVisibility(View.GONE); - - // Show button. - doReturn(View.GONE).when(mButton).getVisibility(); - mLayout.updateVisibility(true /* show */); - - verify(mLayout, never()).createSizeCompatButton(anyBoolean()); - verify(mButton).setVisibility(View.VISIBLE); - } - - @Test - public void testAttachToParentSurface() { - final SurfaceControl.Builder b = new SurfaceControl.Builder(); - mLayout.attachToParentSurface(b); - - verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b); - } - - @Test - public void testOnRestartButtonClicked() { - mLayout.onRestartButtonClicked(); - - verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); - } - - @Test - public void testOnRestartButtonLongClicked_showHint() { - mLayout.dismissHint(); - - assertNull(mLayout.mHint); - - mLayout.onRestartButtonLongClicked(); - - assertNotNull(mLayout.mHint); - } - - @Test - public void testDismissHint() { - mLayout.onRestartButtonLongClicked(); - final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager; - assertNotNull(mLayout.mHint); - assertNotNull(hintWindowManager); - - mLayout.dismissHint(); - - assertNull(mLayout.mHint); - assertNull(mLayout.mHintWindowManager); - verify(hintWindowManager).release(); - } -} diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 147e73599cad..7f6fb90297b3 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -460,4 +460,8 @@ interface IAudioService { boolean register); void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected); + + List<AudioFocusInfo> getFocusStack(); + + boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb); } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 0f08d79bf3bb..3ba1d1f0eca2 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -19,6 +19,7 @@ package android.media.audiopolicy; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; @@ -230,7 +231,7 @@ public class AudioPolicy { * If set to {@code true}, it is mandatory to set an * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build * an {@code AudioPolicy} instance. - * @param enforce true if the policy will govern audio focus decisions. + * @param isFocusPolicy true if the policy will govern audio focus decisions. * @return the same Builder instance. */ @NonNull @@ -723,6 +724,45 @@ public class AudioPolicy { } /** + * Returns the list of entries in the focus stack. + * The list is ordered with increasing rank of focus ownership, where the last entry is at the + * top of the focus stack and is the current focus owner. + * @return the ordered list of focus owners + * @see AudioManager#registerAudioPolicy(AudioPolicy) + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull List<AudioFocusInfo> getFocusStack() { + try { + return getService().getFocusStack(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Send AUDIOFOCUS_LOSS to a specific stack entry, causing it to be notified of the focus + * loss, and for it to exit the focus stack (its focus listener will not be invoked after that). + * This operation is only valid for a registered policy (with + * {@link AudioManager#registerAudioPolicy(AudioPolicy)}) that is also set as the policy focus + * listener (with {@link Builder#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. + * @param focusLoser the stack entry that is exiting the stack through a focus loss + * @return false if the focusLoser wasn't found in the stack, true otherwise + * @throws IllegalStateException if used on an unregistered policy, or a registered policy + * with no {@link AudioPolicyFocusListener} set + * @see AudioManager#registerAudioPolicy(AudioPolicy) + * @see Builder#setAudioPolicyStatusListener(AudioPolicyStatusListener) + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) throws IllegalStateException { + Objects.requireNonNull(focusLoser); + try { + return getService().sendFocusLoss(focusLoser, cb()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. * Audio buffers recorded through the created instance will contain the mix of the audio * streams that fed the given mixer. diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 94de7fa17ae7..255b391b2259 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -285,7 +285,7 @@ public class Tuner implements AutoCloseable { @Nullable private FrontendInfo mFrontendInfo; private Integer mFrontendHandle; - private Boolean mIsSharedFrontend = false; + private Tuner mFeOwnerTuner = null; private int mFrontendType = FrontendSettings.TYPE_UNDEFINED; private int mUserId; private Lnb mLnb; @@ -442,11 +442,10 @@ public class Tuner implements AutoCloseable { mFrontendLock.lock(); try { mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId); - synchronized (mIsSharedFrontend) { - mFrontendHandle = tuner.mFrontendHandle; - mFrontend = tuner.mFrontend; - mIsSharedFrontend = true; - } + mFeOwnerTuner = tuner; + mFeOwnerTuner.registerFrontendCallbackListener(this); + mFrontendHandle = mFeOwnerTuner.mFrontendHandle; + mFrontend = mFeOwnerTuner.mFrontend; nativeShareFrontend(mFrontend.mId); } finally { releaseTRMSLock(); @@ -513,6 +512,27 @@ public class Tuner implements AutoCloseable { private long mNativeContext; // used by native jMediaTuner /** + * Registers a tuner as a listener for frontend callbacks. + */ + private void registerFrontendCallbackListener(Tuner tuner) { + nativeRegisterFeCbListener(tuner.getNativeContext()); + } + + /** + * Unregisters a tuner as a listener for frontend callbacks. + */ + private void unregisterFrontendCallbackListener(Tuner tuner) { + nativeUnregisterFeCbListener(tuner.getNativeContext()); + } + + /** + * Returns the pointer to the associated JTuner. + */ + long getNativeContext() { + return mNativeContext; + } + + /** * Releases the Tuner instance. */ @Override @@ -526,19 +546,21 @@ public class Tuner implements AutoCloseable { } } - private void releaseAll() { + private void releaseFrontend() { mFrontendLock.lock(); try { if (mFrontendHandle != null) { - synchronized (mIsSharedFrontend) { - if (!mIsSharedFrontend) { - int res = nativeCloseFrontend(mFrontendHandle); - if (res != Tuner.RESULT_SUCCESS) { - TunerUtils.throwExceptionForResult(res, "failed to close frontend"); - } - mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); + if (mFeOwnerTuner != null) { + // unregister self from the Frontend callback + mFeOwnerTuner.unregisterFrontendCallbackListener(this); + mFeOwnerTuner = null; + } else { + // close resource as owner + int res = nativeCloseFrontend(mFrontendHandle); + if (res != Tuner.RESULT_SUCCESS) { + TunerUtils.throwExceptionForResult(res, "failed to close frontend"); } - mIsSharedFrontend = false; + mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); } FrameworkStatsLog .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, @@ -549,9 +571,14 @@ public class Tuner implements AutoCloseable { } finally { mFrontendLock.unlock(); } + } + + private void releaseAll() { + releaseFrontend(); mLnbLock.lock(); try { + // mLnb will be non-null only for owner tuner if (mLnb != null) { mLnb.close(); } @@ -641,6 +668,8 @@ public class Tuner implements AutoCloseable { */ private native Frontend nativeOpenFrontendByHandle(int handle); private native int nativeShareFrontend(int id); + private native void nativeRegisterFeCbListener(long nativeContext); + private native void nativeUnregisterFeCbListener(long nativeContext); @Result private native int nativeTune(int type, FrontendSettings settings); private native int nativeStopTune(); @@ -1276,7 +1305,7 @@ public class Tuner implements AutoCloseable { } private void onFrontendEvent(int eventType) { - Log.d(TAG, "Got event from tuning. Event type: " + eventType); + Log.d(TAG, "Got event from tuning. Event type: " + eventType + " for " + this); synchronized (mOnTuneEventLock) { if (mOnTuneEventExecutor != null && mOnTuneEventListener != null) { mOnTuneEventExecutor.execute(() -> { diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index ded9652622a7..c230df30accf 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -951,20 +951,45 @@ FilterClientCallbackImpl::~FilterClientCallbackImpl() { } /////////////// FrontendClientCallbackImpl /////////////////////// -FrontendClientCallbackImpl::FrontendClientCallbackImpl(jweak tunerObj) : mObject(tunerObj) {} +FrontendClientCallbackImpl::FrontendClientCallbackImpl(JTuner* jtuner, jweak listener) { + ALOGV("FrontendClientCallbackImpl() with listener:%p", listener); + addCallbackListener(jtuner, listener); +} + +void FrontendClientCallbackImpl::addCallbackListener(JTuner* jtuner, jweak listener) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jweak listenerRef = env->NewWeakGlobalRef(listener); + ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", listener, listenerRef, this); + std::scoped_lock<std::mutex> lock(mMutex); + mListenersMap[jtuner] = listenerRef; +} + +void FrontendClientCallbackImpl::removeCallbackListener(JTuner* listener) { + ALOGV("removeCallbackListener for listener:%p", listener); + JNIEnv *env = AndroidRuntime::getJNIEnv(); + std::scoped_lock<std::mutex> lock(mMutex); + if (mListenersMap.find(listener) != mListenersMap.end() && mListenersMap[listener]) { + env->DeleteWeakGlobalRef(mListenersMap[listener]); + mListenersMap.erase(listener); + } +} void FrontendClientCallbackImpl::onEvent(FrontendEventType frontendEventType) { ALOGV("FrontendClientCallbackImpl::onEvent, type=%d", frontendEventType); JNIEnv *env = AndroidRuntime::getJNIEnv(); - jobject frontend(env->NewLocalRef(mObject)); - if (!env->IsSameObject(frontend, nullptr)) { - env->CallVoidMethod( - frontend, - gFields.onFrontendEventID, - (jint)frontendEventType); - } else { - ALOGE("FrontendClientCallbackImpl::onEvent:" - "Frontend object has been freed. Ignoring callback."); + std::scoped_lock<std::mutex> lock(mMutex); + for (const auto& mapEntry : mListenersMap) { + ALOGV("JTuner:%p, jweak:%p", mapEntry.first, mapEntry.second); + jobject frontend(env->NewLocalRef(mapEntry.second)); + if (!env->IsSameObject(frontend, nullptr)) { + env->CallVoidMethod( + frontend, + gFields.onFrontendEventID, + (jint)frontendEventType); + } else { + ALOGW("FrontendClientCallbackImpl::onEvent:" + "Frontend object has been freed. Ignoring callback."); + } } } @@ -973,12 +998,25 @@ void FrontendClientCallbackImpl::onScanMessage( ALOGV("FrontendClientCallbackImpl::onScanMessage, type=%d", type); JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass clazz = env->FindClass("android/media/tv/tuner/Tuner"); - jobject frontend(env->NewLocalRef(mObject)); - if (env->IsSameObject(frontend, nullptr)) { - ALOGE("FrontendClientCallbackImpl::onScanMessage:" - "Frontend object has been freed. Ignoring callback."); - return; + + std::scoped_lock<std::mutex> lock(mMutex); + for (const auto& mapEntry : mListenersMap) { + jobject frontend(env->NewLocalRef(mapEntry.second)); + if (env->IsSameObject(frontend, nullptr)) { + ALOGE("FrontendClientCallbackImpl::onScanMessage:" + "Tuner object has been freed. Ignoring callback."); + continue; + } + executeOnScanMessage(env, clazz, frontend, type, message); } +} + +void FrontendClientCallbackImpl::executeOnScanMessage( + JNIEnv *env, const jclass& clazz, const jobject& frontend, + FrontendScanMessageType type, + const FrontendScanMessage& message) { + ALOGV("FrontendClientCallbackImpl::executeOnScanMessage, type=%d", type); + switch(type) { case FrontendScanMessageType::LOCKED: { if (message.get<FrontendScanMessage::Tag::isLocked>()) { @@ -1157,11 +1195,14 @@ void FrontendClientCallbackImpl::onScanMessage( } FrontendClientCallbackImpl::~FrontendClientCallbackImpl() { - JNIEnv *env = AndroidRuntime::getJNIEnv(); - if (mObject != nullptr) { - env->DeleteWeakGlobalRef(mObject); - mObject = nullptr; + JNIEnv *env = android::AndroidRuntime::getJNIEnv(); + ALOGV("~FrontendClientCallbackImpl()"); + std::scoped_lock<std::mutex> lock(mMutex); + for (const auto& mapEntry : mListenersMap) { + ALOGV("deleteRef :%p at @ %p", mapEntry.second, this); + env->DeleteWeakGlobalRef(mapEntry.second); } + mListenersMap.clear(); } /////////////// Tuner /////////////////////// @@ -1180,6 +1221,10 @@ JTuner::JTuner(JNIEnv *env, jobject thiz) : mClass(nullptr) { mSharedFeId = (int)Constant::INVALID_FRONTEND_ID; } +jweak JTuner::getObject() { + return mObject; +} + JTuner::~JTuner() { if (mFeClient != nullptr) { mFeClient->close(); @@ -1192,6 +1237,7 @@ JTuner::~JTuner() { env->DeleteWeakGlobalRef(mObject); env->DeleteGlobalRef(mClass); mFeClient = nullptr; + mFeClientCb = nullptr; mDemuxClient = nullptr; mClass = nullptr; mObject = nullptr; @@ -1247,9 +1293,8 @@ jobject JTuner::openFrontendByHandle(int feHandle) { return nullptr; } - sp<FrontendClientCallbackImpl> feClientCb = - new FrontendClientCallbackImpl(env->NewWeakGlobalRef(mObject)); - mFeClient->setCallback(feClientCb); + mFeClientCb = new FrontendClientCallbackImpl(this, mObject); + mFeClient->setCallback(mFeClientCb); // TODO: add more fields to frontend return env->NewObject( env->FindClass("android/media/tv/tuner/Tuner$Frontend"), @@ -1269,6 +1314,18 @@ int JTuner::shareFrontend(int feId) { return (int)Result::SUCCESS; } +void JTuner::registerFeCbListener(JTuner* jtuner) { + if (mFeClientCb != nullptr && jtuner != nullptr) { + mFeClientCb->addCallbackListener(jtuner, jtuner->getObject()); + } +} + +void JTuner::unregisterFeCbListener(JTuner* jtuner) { + if (mFeClientCb != nullptr && jtuner != nullptr) { + mFeClientCb->removeCallbackListener(jtuner); + } +} + jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); @@ -3188,6 +3245,20 @@ static int android_media_tv_Tuner_share_frontend( return tuner->shareFrontend(id); } +static void android_media_tv_Tuner_register_fe_cb_listener( + JNIEnv *env, jobject thiz, jlong shareeJTuner) { + sp<JTuner> tuner = getTuner(env, thiz); + JTuner *jtuner = (JTuner *)shareeJTuner; + tuner->registerFeCbListener(jtuner); +} + +static void android_media_tv_Tuner_unregister_fe_cb_listener( + JNIEnv *env, jobject thiz, jlong shareeJTuner) { + sp<JTuner> tuner = getTuner(env, thiz); + JTuner *jtuner = (JTuner *)shareeJTuner; + tuner->unregisterFeCbListener(jtuner); +} + static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) { sp<JTuner> tuner = getTuner(env, thiz); FrontendSettings setting = getFrontendSettings(env, type, settings); @@ -4361,6 +4432,10 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_frontend_by_handle }, { "nativeShareFrontend", "(I)I", (void *)android_media_tv_Tuner_share_frontend }, + { "nativeRegisterFeCbListener", "(J)V", + (void*)android_media_tv_Tuner_register_fe_cb_listener }, + { "nativeUnregisterFeCbListener", "(J)V", + (void*)android_media_tv_Tuner_unregister_fe_cb_listener }, { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I", (void *)android_media_tv_Tuner_tune }, { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune }, diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 31d24ee13cf7..06e249212444 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -149,14 +149,21 @@ private: void getRestartEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event); }; +struct JTuner; struct FrontendClientCallbackImpl : public FrontendClientCallback { - FrontendClientCallbackImpl(jweak tunerObj); + FrontendClientCallbackImpl(JTuner*, jweak); ~FrontendClientCallbackImpl(); virtual void onEvent(FrontendEventType frontendEventType); virtual void onScanMessage( FrontendScanMessageType type, const FrontendScanMessage& message); - jweak mObject; + void executeOnScanMessage(JNIEnv *env, const jclass& clazz, const jobject& frontend, + FrontendScanMessageType type, + const FrontendScanMessage& message); + void addCallbackListener(JTuner*, jweak obj); + void removeCallbackListener(JTuner* jtuner); + std::unordered_map<JTuner*, jweak> mListenersMap; + std::mutex mMutex; }; struct JTuner : public RefBase { @@ -171,6 +178,8 @@ struct JTuner : public RefBase { jobject getFrontendIds(); jobject openFrontendByHandle(int feHandle); int shareFrontend(int feId); + void registerFeCbListener(JTuner* jtuner); + void unregisterFeCbListener(JTuner* jtuner); jint closeFrontendById(int id); jobject getFrontendInfo(int id); int tune(const FrontendSettings& settings); @@ -192,6 +201,8 @@ struct JTuner : public RefBase { jint closeFrontend(); jint closeDemux(); + jweak getObject(); + protected: virtual ~JTuner(); @@ -200,6 +211,7 @@ private: jweak mObject; static sp<TunerClient> mTunerClient; sp<FrontendClient> mFeClient; + sp<FrontendClientCallbackImpl> mFeClientCb; int mFeId; int mSharedFeId; sp<LnbClient> mLnbClient; diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index ceba4d60bed0..f76811ee1062 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -364,7 +364,7 @@ void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); - sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer)); + sp<GraphicBuffer> graphic_buffer(GraphicBuffer::fromAHardwareBuffer(buffer)); std::optional<sp<Fence>> fence = std::nullopt; if (acquire_fence_fd != -1) { diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 3e65df23fb2b..632dfb397d51 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -27,6 +27,7 @@ <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" /> <uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" /> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <application android:label="@string/app_name" diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp index 9c07f399f92e..3e82b28ce67e 100644 --- a/packages/ConnectivityT/framework-t/Android.bp +++ b/packages/ConnectivityT/framework-t/Android.bp @@ -103,12 +103,24 @@ filegroup { // Connectivity-T common libraries. filegroup { + name: "framework-connectivity-tiramisu-internal-sources", + srcs: [ + "src/android/net/ConnectivityFrameworkInitializerTiramisu.java", + ], + path: "src", + visibility: [ + "//visibility:private", + ], +} + +filegroup { name: "framework-connectivity-tiramisu-sources", srcs: [ ":framework-connectivity-netstats-sources", ":framework-connectivity-nsd-sources", + ":framework-connectivity-tiramisu-internal-sources", ], visibility: [ "//frameworks/base", ], -}
\ No newline at end of file +} diff --git a/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java new file mode 100644 index 000000000000..630f902ecfd7 --- /dev/null +++ b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; +import android.app.SystemServiceRegistry; +import android.content.Context; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; + +/** + * Class for performing registration for Connectivity services which are exposed via updatable APIs + * since Android T. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class ConnectivityFrameworkInitializerTiramisu { + private ConnectivityFrameworkInitializerTiramisu() {} + + /** + * Called by {@link SystemServiceRegistry}'s static initializer and registers nsd services to + * {@link Context}, so that {@link Context#getSystemService} can return them. + * + * @throws IllegalStateException if this is called anywhere besides + * {@link SystemServiceRegistry}. + */ + public static void registerServiceWrappers() { + SystemServiceRegistry.registerContextAwareService( + Context.NSD_SERVICE, + NsdManager.class, + (context, serviceBinder) -> { + INsdManager service = INsdManager.Stub.asInterface(serviceBinder); + return new NsdManager(context, service); + } + ); + } +} diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java index 6c597e26e042..0f21e55b9f27 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java @@ -16,10 +16,6 @@ package android.net.nsd; -import static com.android.internal.util.Preconditions.checkArgument; -import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.internal.util.Preconditions.checkStringNotEmpty; - import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; @@ -32,11 +28,13 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Protocol; + +import java.util.Objects; /** * The Network Service Discovery Manager class provides the API to discover services @@ -175,65 +173,63 @@ public final class NsdManager { */ public static final int NSD_STATE_ENABLED = 2; - private static final int BASE = Protocol.BASE_NSD_MANAGER; - /** @hide */ - public static final int DISCOVER_SERVICES = BASE + 1; + public static final int DISCOVER_SERVICES = 1; /** @hide */ - public static final int DISCOVER_SERVICES_STARTED = BASE + 2; + public static final int DISCOVER_SERVICES_STARTED = 2; /** @hide */ - public static final int DISCOVER_SERVICES_FAILED = BASE + 3; + public static final int DISCOVER_SERVICES_FAILED = 3; /** @hide */ - public static final int SERVICE_FOUND = BASE + 4; + public static final int SERVICE_FOUND = 4; /** @hide */ - public static final int SERVICE_LOST = BASE + 5; + public static final int SERVICE_LOST = 5; /** @hide */ - public static final int STOP_DISCOVERY = BASE + 6; + public static final int STOP_DISCOVERY = 6; /** @hide */ - public static final int STOP_DISCOVERY_FAILED = BASE + 7; + public static final int STOP_DISCOVERY_FAILED = 7; /** @hide */ - public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8; + public static final int STOP_DISCOVERY_SUCCEEDED = 8; /** @hide */ - public static final int REGISTER_SERVICE = BASE + 9; + public static final int REGISTER_SERVICE = 9; /** @hide */ - public static final int REGISTER_SERVICE_FAILED = BASE + 10; + public static final int REGISTER_SERVICE_FAILED = 10; /** @hide */ - public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11; + public static final int REGISTER_SERVICE_SUCCEEDED = 11; /** @hide */ - public static final int UNREGISTER_SERVICE = BASE + 12; + public static final int UNREGISTER_SERVICE = 12; /** @hide */ - public static final int UNREGISTER_SERVICE_FAILED = BASE + 13; + public static final int UNREGISTER_SERVICE_FAILED = 13; /** @hide */ - public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14; + public static final int UNREGISTER_SERVICE_SUCCEEDED = 14; /** @hide */ - public static final int RESOLVE_SERVICE = BASE + 18; + public static final int RESOLVE_SERVICE = 15; /** @hide */ - public static final int RESOLVE_SERVICE_FAILED = BASE + 19; + public static final int RESOLVE_SERVICE_FAILED = 16; /** @hide */ - public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20; + public static final int RESOLVE_SERVICE_SUCCEEDED = 17; /** @hide */ - public static final int DAEMON_CLEANUP = BASE + 21; + public static final int DAEMON_CLEANUP = 18; /** @hide */ - public static final int DAEMON_STARTUP = BASE + 22; + public static final int DAEMON_STARTUP = 19; /** @hide */ - public static final int ENABLE = BASE + 24; + public static final int ENABLE = 20; /** @hide */ - public static final int DISABLE = BASE + 25; + public static final int DISABLE = 21; /** @hide */ - public static final int NATIVE_DAEMON_EVENT = BASE + 26; + public static final int NATIVE_DAEMON_EVENT = 22; /** @hide */ - public static final int REGISTER_CLIENT = BASE + 27; + public static final int REGISTER_CLIENT = 23; /** @hide */ - public static final int UNREGISTER_CLIENT = BASE + 28; + public static final int UNREGISTER_CLIENT = 24; /** Dns based service discovery protocol */ public static final int PROTOCOL_DNS_SD = 0x0001; @@ -550,7 +546,9 @@ public final class NsdManager { final int key; synchronized (mMapLock) { int valueIndex = mListenerMap.indexOfValue(listener); - checkArgument(valueIndex == -1, "listener already in use"); + if (valueIndex != -1) { + throw new IllegalArgumentException("listener already in use"); + } key = nextListenerKey(); mListenerMap.put(key, listener); mServiceMap.put(key, s); @@ -569,7 +567,9 @@ public final class NsdManager { checkListener(listener); synchronized (mMapLock) { int valueIndex = mListenerMap.indexOfValue(listener); - checkArgument(valueIndex != -1, "listener not registered"); + if (valueIndex == -1) { + throw new IllegalArgumentException("listener not registered"); + } return mListenerMap.keyAt(valueIndex); } } @@ -598,7 +598,9 @@ public final class NsdManager { */ public void registerService(NsdServiceInfo serviceInfo, int protocolType, RegistrationListener listener) { - checkArgument(serviceInfo.getPort() > 0, "Invalid port number"); + if (serviceInfo.getPort() <= 0) { + throw new IllegalArgumentException("Invalid port number"); + } checkServiceInfo(serviceInfo); checkProtocol(protocolType); int key = putListener(listener, serviceInfo); @@ -660,7 +662,9 @@ public final class NsdManager { * Cannot be null. Cannot be in use for an active service discovery. */ public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) { - checkStringNotEmpty(serviceType, "Service type cannot be empty"); + if (TextUtils.isEmpty(serviceType)) { + throw new IllegalArgumentException("Service type cannot be empty"); + } checkProtocol(protocolType); NsdServiceInfo s = new NsdServiceInfo(); @@ -719,16 +723,22 @@ public final class NsdManager { } private static void checkListener(Object listener) { - checkNotNull(listener, "listener cannot be null"); + Objects.requireNonNull(listener, "listener cannot be null"); } private static void checkProtocol(int protocolType) { - checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol"); + if (protocolType != PROTOCOL_DNS_SD) { + throw new IllegalArgumentException("Unsupported protocol"); + } } private static void checkServiceInfo(NsdServiceInfo serviceInfo) { - checkNotNull(serviceInfo, "NsdServiceInfo cannot be null"); - checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty"); - checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty"); + Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null"); + if (TextUtils.isEmpty(serviceInfo.getServiceName())) { + throw new IllegalArgumentException("Service name cannot be empty"); + } + if (TextUtils.isEmpty(serviceInfo.getServiceType())) { + throw new IllegalArgumentException("Service type cannot be empty"); + } } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index a1b3df87f8d4..1e9a41e4d0b8 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -218,7 +218,6 @@ <!-- notifications & DND access --> <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" /> - <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" /> <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml index 67a70bb39964..b3987f1aeeda 100644 --- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -94,4 +94,9 @@ android:fromId="@id/unlocked" android:toId="@id/unlocked_aod" android:drawable="@drawable/unlocked_ls_to_aod" /> + + <transition + android:fromId="@id/unlocked" + android:toId="@id/locked_aod" + android:drawable="@drawable/unlocked_to_aod_lock" /> </animated-selector> diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml new file mode 100644 index 000000000000..b6d76e01ad95 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_2_G_T_1" android:translateX="22.75" android:translateY="22.25" android:scaleX="1.02" android:scaleY="1.02"> + <group android:name="_R_G_L_2_G" android:translateX="-8.75" android:translateY="-8.75"> + <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:strokeAlpha="1" + android:pathData=" M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " /> + </group> + </group> + <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " /> + </group> + <group android:name="_R_G_L_0_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" android:startOffset="0" android:valueFrom="2.5" android:valueTo="2" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="83" android:startOffset="0" android:valueFrom="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueTo="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.456,0 0.464,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" + android:duration="133" android:startOffset="83" android:valueFrom="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueTo="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.606,0 0.035,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" + android:duration="117" android:startOffset="217" android:valueFrom="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueTo="M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.511,0 0.409,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_2_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" + android:duration="333" android:startOffset="0" android:valueFrom="22.75" android:valueTo="23" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_2_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="0" android:valueFrom="22.25" android:valueTo="25.5" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_2_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="scaleX" + android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" + android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" android:startOffset="0" android:valueFrom="2" android:valueTo="1.5" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" android:startOffset="0" android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" android:startOffset="0" android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" + android:duration="850" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml index a2b8bf6d3d4c..4f0925f3bfbb 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml @@ -20,19 +20,18 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/keyguard_bouncer_user_switcher" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" android:orientation="vertical" android:gravity="center" - android:paddingTop="12dp" android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent from this view when bouncer is shown --> - <ImageView - android:id="@+id/user_icon" - android:layout_width="@dimen/keyguard_user_switcher_icon_size" - android:layout_height="@dimen/keyguard_user_switcher_icon_size" /> + <ImageView + android:id="@+id/user_icon" + android:layout_width="@dimen/keyguard_user_switcher_icon_size" + android:layout_height="@dimen/keyguard_user_switcher_icon_size" /> <!-- need to keep this outer view in order to have a correctly sized anchor for the dropdown menu, as well as dropdown background in the right place --> @@ -41,7 +40,7 @@ android:orientation="horizontal" android:layout_height="wrap_content" android:layout_width="wrap_content" - android:layout_marginTop="32dp" + android:layout_marginTop="30dp" android:minHeight="48dp"> <TextView style="@style/Keyguard.UserSwitcher.Spinner.Header" diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 95330400145f..2819dc9c27b7 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -108,10 +108,10 @@ <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen> - <dimen name="keyguard_user_switcher_header_text_size">24sp</dimen> - <dimen name="keyguard_user_switcher_item_text_size">18sp</dimen> - <dimen name="keyguard_user_switcher_width">300dp</dimen> - <dimen name="keyguard_user_switcher_icon_size">250dp</dimen> + <dimen name="keyguard_user_switcher_header_text_size">32sp</dimen> + <dimen name="keyguard_user_switcher_item_text_size">32sp</dimen> + <dimen name="keyguard_user_switcher_width">320dp</dimen> + <dimen name="keyguard_user_switcher_icon_size">310dp</dimen> <dimen name="keyguard_user_switcher_corner">32dp</dimen> <dimen name="keyguard_user_switcher_popup_corner">24dp</dimen> <dimen name="keyguard_user_switcher_item_padding_vertical">15dp</dimen> diff --git a/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml new file mode 100644 index 000000000000..a93abb71c9d7 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml @@ -0,0 +1,24 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M18.575,15.2L7.55,4.175q1.1,-0.325 2.212,-0.462 1.113,-0.138 2.238,-0.138 3.45,0 6.55,1.45Q21.65,6.475 24,9zM20.225,23.575l-4.75,-4.75 -3.475,4.1L0,9q0.675,-0.725 1.438,-1.4 0.762,-0.675 1.612,-1.225l-2.625,-2.6L2.1,2.1l19.8,19.8z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml new file mode 100644 index 000000000000..b611ffa6d84e --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. +--> +<com.android.systemui.dreams.DreamOverlayContainerView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/dream_overlay_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/dream_overlay_content" + android:layout_width="match_parent" + android:layout_height="0dp" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> + + <com.android.systemui.dreams.DreamOverlayStatusBarView + android:id="@+id/dream_overlay_status_bar" + android:layout_width="match_parent" + android:layout_height="@dimen/dream_overlay_status_bar_height" + android:layout_marginEnd="@dimen/dream_overlay_status_bar_margin" + android:layout_marginStart="@dimen/dream_overlay_status_bar_margin" + app:layout_constraintTop_toTopOf="parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/dream_overlay_system_status" + android:layout_width="wrap_content" + android:layout_height="match_parent" + app:layout_constraintEnd_toEndOf="parent"> + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/dream_overlay_wifi_status" + android:layout_width="@dimen/status_bar_wifi_signal_size" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:visibility="gone" + app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" /> + + <com.android.systemui.battery.BatteryMeterView + android:id="@+id/dream_overlay_battery" + android:layout_width="wrap_content" + android:layout_height="match_parent" + app:layout_constraintEnd_toEndOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + </com.android.systemui.dreams.DreamOverlayStatusBarView> +</com.android.systemui.dreams.DreamOverlayContainerView>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml index f057603e2cd0..1d6f279afc66 100644 --- a/packages/SystemUI/res/values-h800dp/dimens.xml +++ b/packages/SystemUI/res/values-h800dp/dimens.xml @@ -22,5 +22,5 @@ <dimen name="large_clock_text_size">200dp</dimen> <!-- With the large clock, move up slightly from the center --> - <dimen name="keyguard_large_clock_top_margin">-104dp</dimen> + <dimen name="keyguard_large_clock_top_margin">-112dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c7350a1eee8b..d4398a8d35a2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -30,10 +30,10 @@ <dimen name="navigation_bar_deadzone_size_max">32dp</dimen> <!-- dimensions for the navigation bar handle --> - <dimen name="navigation_handle_radius">1dp</dimen> - <dimen name="navigation_handle_bottom">6dp</dimen> + <dimen name="navigation_handle_radius">2dp</dimen> + <dimen name="navigation_handle_bottom">10dp</dimen> <dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen> - <dimen name="navigation_home_handle_width">72dp</dimen> + <dimen name="navigation_home_handle_width">108dp</dimen> <!-- Size of the nav bar edge panels, should be greater to the edge sensitivity + the drag threshold --> @@ -602,7 +602,7 @@ <!-- When large clock is showing, offset the smartspace by this amount --> <dimen name="keyguard_smartspace_top_offset">12dp</dimen> <!-- With the large clock, move up slightly from the center --> - <dimen name="keyguard_large_clock_top_margin">-52dp</dimen> + <dimen name="keyguard_large_clock_top_margin">-60dp</dimen> <!-- Default line spacing multiplier between hours and minutes of the keyguard clock --> <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item> @@ -1303,4 +1303,9 @@ <dimen name="keyguard_unfold_translation_x">16dp</dimen> <dimen name="fgs_manager_min_width_minor">100%</dimen> + + <!-- Dream overlay related dimensions --> + <dimen name="dream_overlay_status_bar_height">80dp</dimen> + <dimen name="dream_overlay_status_bar_margin">40dp</dimen> + <dimen name="dream_overlay_status_icon_margin">8dp</dimen> </resources> 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 30db13611f4a..c1d9d0d0e142 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 @@ -65,6 +65,7 @@ public class RemoteAnimationTargetCompat { public final Rect localBounds; public final Rect sourceContainerBounds; public final Rect screenSpaceBounds; + public final Rect startScreenSpaceBounds; public final boolean isNotInRecents; public final Rect contentInsets; public final ActivityManager.RunningTaskInfo taskInfo; @@ -88,6 +89,7 @@ public class RemoteAnimationTargetCompat { localBounds = app.localBounds; sourceContainerBounds = app.sourceContainerBounds; screenSpaceBounds = app.screenSpaceBounds; + startScreenSpaceBounds = screenSpaceBounds; prefixOrderIndex = app.prefixOrderIndex; isNotInRecents = app.isNotInRecents; contentInsets = app.contentInsets; @@ -219,6 +221,8 @@ public class RemoteAnimationTargetCompat { localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y); sourceContainerBounds = null; screenSpaceBounds = new Rect(change.getEndAbsBounds()); + startScreenSpaceBounds = new Rect(change.getStartAbsBounds()); + prefixOrderIndex = order; // TODO(shell-transitions): I guess we need to send content insets? evaluate how its used. contentInsets = new Rect(0, 0, 0, 0); diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index ac463ebc1c97..157191302010 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -168,6 +168,12 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie if (!mIsDozing) mView.animateAppearOnLockscreen(); } + /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to + * fully folded state and it goes to sleep (always on display screen) */ + public void animateFoldAppear() { + mView.animateFoldAppear(); + } + /** * Updates the time for the view. */ diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java deleted file mode 100644 index 2a0c2855c3b2..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2020 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.keyguard; - -import android.annotation.FloatRange; -import android.annotation.IntRange; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.text.format.DateFormat; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.android.systemui.R; - -import java.util.Calendar; -import java.util.Locale; -import java.util.TimeZone; - -import kotlin.Unit; - -/** - * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30) - * The time's text color is a gradient that changes its colors based on its controller. - */ -public class AnimatableClockView extends TextView { - private static final CharSequence DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"; - private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"; - private static final long DOZE_ANIM_DURATION = 300; - private static final long APPEAR_ANIM_DURATION = 350; - private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500; - private static final long CHARGE_ANIM_DURATION_PHASE_1 = 1000; - - private final Calendar mTime = Calendar.getInstance(); - - private final int mDozingWeight; - private final int mLockScreenWeight; - private CharSequence mFormat; - private CharSequence mDescFormat; - private int mDozingColor; - private int mLockScreenColor; - private float mLineSpacingScale = 1f; - private int mChargeAnimationDelay = 0; - - private TextAnimator mTextAnimator = null; - private Runnable mOnTextAnimatorInitialized; - - private boolean mIsSingleLine; - - public AnimatableClockView(Context context) { - this(context, null, 0, 0); - } - - public AnimatableClockView(Context context, AttributeSet attrs) { - this(context, attrs, 0, 0); - } - - public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - TypedArray ta = context.obtainStyledAttributes( - attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes); - try { - mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100); - mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300); - mChargeAnimationDelay = ta.getInt( - R.styleable.AnimatableClockView_chargeAnimationDelay, 200); - } finally { - ta.recycle(); - } - - ta = context.obtainStyledAttributes( - attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes); - try { - mIsSingleLine = ta.getBoolean(android.R.styleable.TextView_singleLine, false); - } finally { - ta.recycle(); - } - - refreshFormat(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - refreshFormat(); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - } - - int getDozingWeight() { - if (useBoldedVersion()) { - return mDozingWeight + 100; - } - return mDozingWeight; - } - - int getLockScreenWeight() { - if (useBoldedVersion()) { - return mLockScreenWeight + 100; - } - return mLockScreenWeight; - } - - /** - * Whether to use a bolded version based on the user specified fontWeightAdjustment. - */ - boolean useBoldedVersion() { - // "Bold text" fontWeightAdjustment is 300. - return getResources().getConfiguration().fontWeightAdjustment > 100; - } - - void refreshTime() { - mTime.setTimeInMillis(System.currentTimeMillis()); - setText(DateFormat.format(mFormat, mTime)); - setContentDescription(DateFormat.format(mDescFormat, mTime)); - } - - void onTimeZoneChanged(TimeZone timeZone) { - mTime.setTimeZone(timeZone); - refreshFormat(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mTextAnimator == null) { - mTextAnimator = new TextAnimator( - getLayout(), - () -> { - invalidate(); - return Unit.INSTANCE; - }); - if (mOnTextAnimatorInitialized != null) { - mOnTextAnimatorInitialized.run(); - mOnTextAnimatorInitialized = null; - } - } else { - mTextAnimator.updateLayout(getLayout()); - } - } - - @Override - protected void onDraw(Canvas canvas) { - mTextAnimator.draw(canvas); - } - - void setLineSpacingScale(float scale) { - mLineSpacingScale = scale; - setLineSpacing(0, mLineSpacingScale); - } - - void setColors(int dozingColor, int lockScreenColor) { - mDozingColor = dozingColor; - mLockScreenColor = lockScreenColor; - } - - void animateAppearOnLockscreen() { - if (mTextAnimator == null) { - return; - } - - setTextStyle( - getDozingWeight(), - -1 /* text size, no update */, - mLockScreenColor, - false /* animate */, - 0 /* duration */, - 0 /* delay */, - null /* onAnimationEnd */); - - setTextStyle( - getLockScreenWeight(), - -1 /* text size, no update */, - mLockScreenColor, - true, /* animate */ - APPEAR_ANIM_DURATION, - 0 /* delay */, - null /* onAnimationEnd */); - } - - void animateCharge(DozeStateGetter dozeStateGetter) { - if (mTextAnimator == null || mTextAnimator.isRunning()) { - // Skip charge animation if dozing animation is already playing. - return; - } - Runnable startAnimPhase2 = () -> setTextStyle( - dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */, - -1, - null, - true /* animate */, - CHARGE_ANIM_DURATION_PHASE_1, - 0 /* delay */, - null /* onAnimationEnd */); - setTextStyle(dozeStateGetter.isDozing() - ? getLockScreenWeight() - : getDozingWeight()/* weight */, - -1, - null, - true /* animate */, - CHARGE_ANIM_DURATION_PHASE_0, - mChargeAnimationDelay, - startAnimPhase2); - } - - void animateDoze(boolean isDozing, boolean animate) { - setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */, - -1, - isDozing ? mDozingColor : mLockScreenColor, - animate, - DOZE_ANIM_DURATION, - 0 /* delay */, - null /* onAnimationEnd */); - } - - /** - * Set text style with an optional animation. - * - * By passing -1 to weight, the view preserves its current weight. - * By passing -1 to textSize, the view preserves its current text size. - * - * @param weight text weight. - * @param textSize font size. - * @param animate true to animate the text style change, otherwise false. - */ - private void setTextStyle( - @IntRange(from = 0, to = 1000) int weight, - @FloatRange(from = 0) float textSize, - Integer color, - boolean animate, - long duration, - long delay, - Runnable onAnimationEnd) { - if (mTextAnimator != null) { - mTextAnimator.setTextStyle(weight, textSize, color, animate, duration, null, - delay, onAnimationEnd); - } else { - // when the text animator is set, update its start values - mOnTextAnimatorInitialized = - () -> mTextAnimator.setTextStyle( - weight, textSize, color, false, duration, null, - delay, onAnimationEnd); - } - } - - void refreshFormat() { - Patterns.update(mContext); - - final boolean use24HourFormat = DateFormat.is24HourFormat(getContext()); - if (mIsSingleLine && use24HourFormat) { - mFormat = Patterns.sClockView24; - } else if (!mIsSingleLine && use24HourFormat) { - mFormat = DOUBLE_LINE_FORMAT_24_HOUR; - } else if (mIsSingleLine && !use24HourFormat) { - mFormat = Patterns.sClockView12; - } else { - mFormat = DOUBLE_LINE_FORMAT_12_HOUR; - } - - mDescFormat = use24HourFormat ? Patterns.sClockView24 : Patterns.sClockView12; - refreshTime(); - } - - // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. - // This is an optimization to ensure we only recompute the patterns when the inputs change. - private static final class Patterns { - static String sClockView12; - static String sClockView24; - static String sCacheKey; - - static void update(Context context) { - final Locale locale = Locale.getDefault(); - final Resources res = context.getResources(); - final String clockView12Skel = res.getString(R.string.clock_12hr_format); - final String clockView24Skel = res.getString(R.string.clock_24hr_format); - final String key = locale.toString() + clockView12Skel + clockView24Skel; - if (key.equals(sCacheKey)) return; - sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel); - - // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton - // format. The following code removes the AM/PM indicator if we didn't want it. - if (!clockView12Skel.contains("a")) { - sClockView12 = sClockView12.replaceAll("a", "").trim(); - } - sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel); - sCacheKey = key; - } - } - - interface DozeStateGetter { - boolean isDozing(); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt new file mode 100644 index 000000000000..357be2503a3a --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard + +import android.animation.TimeInterpolator +import android.annotation.ColorInt +import android.annotation.FloatRange +import android.annotation.IntRange +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.text.format.DateFormat +import android.util.AttributeSet +import android.widget.TextView +import com.android.systemui.R +import com.android.systemui.animation.Interpolators +import com.android.systemui.statusbar.notification.stack.StackStateAnimator +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +/** + * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30) + * The time's text color is a gradient that changes its colors based on its controller. + */ +class AnimatableClockView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : TextView(context, attrs, defStyleAttr, defStyleRes) { + + private val time = Calendar.getInstance() + + private val dozingWeightInternal: Int + private val lockScreenWeightInternal: Int + private val isSingleLineInternal: Boolean + + private var format: CharSequence? = null + private var descFormat: CharSequence? = null + + @ColorInt + private var dozingColor = 0 + + @ColorInt + private var lockScreenColor = 0 + + private var lineSpacingScale = 1f + private val chargeAnimationDelay: Int + private var textAnimator: TextAnimator? = null + private var onTextAnimatorInitialized: Runnable? = null + + val dozingWeight: Int + get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal + + val lockScreenWeight: Int + get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal + + init { + val animatableClockViewAttributes = context.obtainStyledAttributes( + attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes + ) + + try { + dozingWeightInternal = animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_dozeWeight, + 100 + ) + lockScreenWeightInternal = animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_lockScreenWeight, + 300 + ) + chargeAnimationDelay = animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_chargeAnimationDelay, 200 + ) + } finally { + animatableClockViewAttributes.recycle() + } + + val textViewAttributes = context.obtainStyledAttributes( + attrs, android.R.styleable.TextView, + defStyleAttr, defStyleRes + ) + + isSingleLineInternal = + try { + textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false) + } finally { + textViewAttributes.recycle() + } + + refreshFormat() + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + refreshFormat() + } + + /** + * Whether to use a bolded version based on the user specified fontWeightAdjustment. + */ + fun useBoldedVersion(): Boolean { + // "Bold text" fontWeightAdjustment is 300. + return resources.configuration.fontWeightAdjustment > 100 + } + + fun refreshTime() { + time.timeInMillis = System.currentTimeMillis() + text = DateFormat.format(format, time) + contentDescription = DateFormat.format(descFormat, time) + } + + fun onTimeZoneChanged(timeZone: TimeZone?) { + time.timeZone = timeZone + refreshFormat() + } + + @SuppressLint("DrawAllocation") + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val animator = textAnimator + if (animator == null) { + textAnimator = TextAnimator(layout) { invalidate() } + onTextAnimatorInitialized?.run() + onTextAnimatorInitialized = null + } else { + animator.updateLayout(layout) + } + } + + override fun onDraw(canvas: Canvas) { + textAnimator?.draw(canvas) + } + + fun setLineSpacingScale(scale: Float) { + lineSpacingScale = scale + setLineSpacing(0f, lineSpacingScale) + } + + fun setColors(@ColorInt dozingColor: Int, lockScreenColor: Int) { + this.dozingColor = dozingColor + this.lockScreenColor = lockScreenColor + } + + fun animateAppearOnLockscreen() { + if (textAnimator == null) { + return + } + setTextStyle( + weight = dozingWeight, + textSize = -1f, + color = lockScreenColor, + animate = false, + duration = 0, + delay = 0, + onAnimationEnd = null + ) + setTextStyle( + weight = lockScreenWeight, + textSize = -1f, + color = lockScreenColor, + animate = true, + duration = APPEAR_ANIM_DURATION, + delay = 0, + onAnimationEnd = null + ) + } + + fun animateFoldAppear() { + if (textAnimator == null) { + return + } + setTextStyle( + weight = lockScreenWeightInternal, + textSize = -1f, + color = lockScreenColor, + animate = false, + duration = 0, + delay = 0, + onAnimationEnd = null + ) + setTextStyle( + weight = dozingWeightInternal, + textSize = -1f, + color = dozingColor, + animate = true, + interpolator = Interpolators.EMPHASIZED_DECELERATE, + duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(), + delay = 0, + onAnimationEnd = null + ) + } + + fun animateCharge(dozeStateGetter: DozeStateGetter) { + if (textAnimator == null || textAnimator!!.isRunning()) { + // Skip charge animation if dozing animation is already playing. + return + } + val startAnimPhase2 = Runnable { + setTextStyle( + weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight, + textSize = -1f, + color = null, + animate = true, + duration = CHARGE_ANIM_DURATION_PHASE_1, + delay = 0, + onAnimationEnd = null + ) + } + setTextStyle( + weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight, + textSize = -1f, + color = null, + animate = true, + duration = CHARGE_ANIM_DURATION_PHASE_0, + delay = chargeAnimationDelay.toLong(), + onAnimationEnd = startAnimPhase2 + ) + } + + fun animateDoze(isDozing: Boolean, animate: Boolean) { + setTextStyle( + weight = if (isDozing) dozingWeight else lockScreenWeight, + textSize = -1f, + color = if (isDozing) dozingColor else lockScreenColor, + animate = animate, + duration = DOZE_ANIM_DURATION, + delay = 0, + onAnimationEnd = null + ) + } + + /** + * Set text style with an optional animation. + * + * By passing -1 to weight, the view preserves its current weight. + * By passing -1 to textSize, the view preserves its current text size. + * + * @param weight text weight. + * @param textSize font size. + * @param animate true to animate the text style change, otherwise false. + */ + private fun setTextStyle( + @IntRange(from = 0, to = 1000) weight: Int, + @FloatRange(from = 0.0) textSize: Float, + color: Int?, + animate: Boolean, + interpolator: TimeInterpolator?, + duration: Long, + delay: Long, + onAnimationEnd: Runnable? + ) { + if (textAnimator != null) { + textAnimator?.setTextStyle( + weight = weight, + textSize = textSize, + color = color, + animate = animate, + duration = duration, + interpolator = interpolator, + delay = delay, + onAnimationEnd = onAnimationEnd + ) + } else { + // when the text animator is set, update its start values + onTextAnimatorInitialized = Runnable { + textAnimator?.setTextStyle( + weight = weight, + textSize = textSize, + color = color, + animate = false, + duration = duration, + interpolator = interpolator, + delay = delay, + onAnimationEnd = onAnimationEnd + ) + } + } + } + + private fun setTextStyle( + @IntRange(from = 0, to = 1000) weight: Int, + @FloatRange(from = 0.0) textSize: Float, + color: Int?, + animate: Boolean, + duration: Long, + delay: Long, + onAnimationEnd: Runnable? + ) { + setTextStyle( + weight = weight, + textSize = textSize, + color = color, + animate = animate, + interpolator = null, + duration = duration, + delay = delay, + onAnimationEnd = onAnimationEnd + ) + } + + fun refreshFormat() { + Patterns.update(context) + val use24HourFormat = DateFormat.is24HourFormat(context) + + format = when { + isSingleLineInternal && use24HourFormat -> Patterns.sClockView24 + !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR + isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 + else -> DOUBLE_LINE_FORMAT_12_HOUR + } + + descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 + + refreshTime() + } + + // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. + // This is an optimization to ensure we only recompute the patterns when the inputs change. + private object Patterns { + var sClockView12: String? = null + var sClockView24: String? = null + var sCacheKey: String? = null + + fun update(context: Context) { + val locale = Locale.getDefault() + val res = context.resources + val clockView12Skel = res.getString(R.string.clock_12hr_format) + val clockView24Skel = res.getString(R.string.clock_24hr_format) + val key = locale.toString() + clockView12Skel + clockView24Skel + if (key == sCacheKey) return + + val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel) + sClockView12 = clockView12 + + // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton + // format. The following code removes the AM/PM indicator if we didn't want it. + if (!clockView12Skel.contains("a")) { + sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' } + } + sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel) + sCacheKey = key + } + } + + interface DozeStateGetter { + val isDozing: Boolean + } +} + +private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" +private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm" +private const val DOZE_ANIM_DURATION: Long = 300 +private const val APPEAR_ANIM_DURATION: Long = 350 +private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500 +private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 9238b8226bbc..25dcdf9aa561 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -190,11 +190,15 @@ public class KeyguardClockSwitch extends RelativeLayout { } } - private void animateClockChange(boolean useLargeClock) { + private void updateClockViews(boolean useLargeClock, boolean animate) { if (mClockInAnim != null) mClockInAnim.cancel(); if (mClockOutAnim != null) mClockOutAnim.cancel(); if (mStatusAreaAnim != null) mStatusAreaAnim.cancel(); + mClockInAnim = null; + mClockOutAnim = null; + mStatusAreaAnim = null; + View in, out; int direction = 1; float statusAreaYTranslation; @@ -214,6 +218,14 @@ public class KeyguardClockSwitch extends RelativeLayout { removeView(out); } + if (!animate) { + out.setAlpha(0f); + in.setAlpha(1f); + in.setVisibility(VISIBLE); + mStatusArea.setTranslationY(statusAreaYTranslation); + return; + } + mClockOutAnim = new AnimatorSet(); mClockOutAnim.setDuration(CLOCK_OUT_MILLIS); mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); @@ -273,7 +285,7 @@ public class KeyguardClockSwitch extends RelativeLayout { * * @return true if desired clock appeared and false if it was already visible */ - boolean switchToClock(@ClockSize int clockSize) { + boolean switchToClock(@ClockSize int clockSize, boolean animate) { if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) { return false; } @@ -281,7 +293,7 @@ public class KeyguardClockSwitch extends RelativeLayout { // let's make sure clock is changed only after all views were laid out so we can // translate them properly if (mChildrenAreLaidOut) { - animateClockChange(clockSize == LARGE); + updateClockViews(clockSize == LARGE, animate); } mDisplayedClockSize = clockSize; @@ -293,7 +305,7 @@ public class KeyguardClockSwitch extends RelativeLayout { super.onLayout(changed, l, t, r, b); if (mDisplayedClockSize != null && !mChildrenAreLaidOut) { - animateClockChange(mDisplayedClockSize == LARGE); + updateClockViews(mDisplayedClockSize == LARGE, /* animate */ true); } mChildrenAreLaidOut = true; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 032da789518c..b7a5aae38515 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -275,11 +275,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void updateClockLayout() { - int largeClockTopMargin = 0; - if (mSmartspaceController.isEnabled()) { - largeClockTopMargin = getContext().getResources().getDimensionPixelSize( - R.dimen.keyguard_large_clock_top_margin); - } + int largeClockTopMargin = getContext().getResources().getDimensionPixelSize( + R.dimen.keyguard_large_clock_top_margin); + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); lp.topMargin = largeClockTopMargin; @@ -290,17 +288,24 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS * Set which clock should be displayed on the keyguard. The other one will be automatically * hidden. */ - public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) { + public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize, boolean animate) { if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) { return; } - boolean appeared = mView.switchToClock(clockSize); - if (appeared && clockSize == LARGE) { + boolean appeared = mView.switchToClock(clockSize, animate); + if (animate && appeared && clockSize == LARGE) { mLargeClockViewController.animateAppear(); } } + public void animateFoldToAod() { + if (mClockViewController != null) { + mClockViewController.animateFoldAppear(); + mLargeClockViewController.animateFoldAppear(); + } + } + /** * If we're presenting a custom clock of just the default one. */ @@ -443,7 +448,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) != 0; if (!mCanShowDoubleLineClock) { - mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL)); + mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true)); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 67ef02a6029b..b84cb19b9468 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -866,7 +866,6 @@ public class KeyguardSecurityContainer extends FrameLayout { ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255); } - anchor.setClickable(true); anchor.setOnClickListener((v) -> { if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; @@ -877,8 +876,7 @@ public class KeyguardSecurityContainer extends FrameLayout { public void onItemClick(AdapterView parent, View view, int pos, long id) { if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; - // - 1 to account for the header view - UserRecord user = adapter.getItem(pos - 1); + UserRecord user = adapter.getItem(pos); if (!user.isCurrent) { adapter.onUserListItemClicked(user); } @@ -907,9 +905,16 @@ public class KeyguardSecurityContainer extends FrameLayout { == Configuration.ORIENTATION_PORTRAIT) { updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL); updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL); + mUserSwitcherViewGroup.setTranslationY(0); } else { updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM); - updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.TOP); + updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL); + + // Attempt to reposition a bit higher to make up for this frame being a bit lower + // on the device + int yTrans = mView.getContext().getResources().getDimensionPixelSize( + R.dimen.status_bar_height); + mUserSwitcherViewGroup.setTranslationY(-yTrans); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 986d0debb56a..8bf890d7df50 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -123,8 +123,17 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Set which clock should be displayed on the keyguard. The other one will be automatically * hidden. */ - public void displayClock(@ClockSize int clockSize) { - mKeyguardClockSwitchController.displayClock(clockSize); + public void displayClock(@ClockSize int clockSize, boolean animate) { + mKeyguardClockSwitchController.displayClock(clockSize, animate); + } + + /** + * Performs fold to aod animation of the clocks (changes font weight from bold to thin). + * This animation is played when AOD is enabled and foldable device is fully folded, it is + * displayed on the outer screen + */ + public void animateFoldToAod() { + mKeyguardClockSwitchController.animateFoldToAod(); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java index dfb466788ca7..7b6ce3e1c951 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java @@ -18,12 +18,10 @@ package com.android.keyguard; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.ListPopupWindow; import android.widget.ListView; -import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.plugins.FalsingManager; @@ -68,12 +66,6 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow { // This will force the popupwindow to show upward instead of drop down listView.addOnLayoutChangeListener(mLayoutListener); - TextView header = (TextView) LayoutInflater.from(mContext).inflate( - R.layout.keyguard_bouncer_user_switcher_item, listView, false); - header.setText(mContext.getResources().getString( - R.string.accessibility_multi_user_switch_switcher)); - listView.addHeaderView(header); - listView.setOnTouchListener((v, ev) -> { if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { return mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index cd57af4ae97b..6626f59aae8c 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -278,10 +278,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setContentDescription(mUnlockedLabel); mView.setVisibility(View.VISIBLE); } else if (mShowAodLockIcon) { - if (wasShowingUnlock) { - // transition to the unlock icon first - mView.updateIcon(ICON_LOCK, false); - } mView.updateIcon(ICON_LOCK, true); mView.setContentDescription(mLockedLabel); mView.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 8c405ca6bd39..63962fa6da11 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.app.ActivityManager; import android.app.ActivityThread; import android.app.Application; import android.app.Notification; @@ -27,6 +28,7 @@ import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.os.Bundle; import android.os.Process; +import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; @@ -111,6 +113,13 @@ public class SystemUIApplication extends Application implements ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG); } + // Enable binder tracing on system server for calls originating from SysUI + try { + ActivityManager.getService().enableBinderTracing(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to enable binder tracing", e); + } + registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 251c1e632f95..276790483861 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -121,7 +121,7 @@ public class SystemUIFactory { .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) - .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI())) + .setCompatUI(Optional.of(mWMComponent.getCompatUI())) .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option @@ -141,7 +141,7 @@ public class SystemUIFactory { .setStartingSurface(Optional.ofNullable(null)) .setTaskSurfaceHelper(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) - .setSizeCompatUI(Optional.ofNullable(null)) + .setCompatUI(Optional.ofNullable(null)) .setDragAndDrop(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java index 4aa46f1813dc..58cf35f2917c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java @@ -16,12 +16,14 @@ package com.android.systemui.communal; +import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS; + import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.communal.conditions.CommunalConditionsMonitor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.condition.Monitor; import com.google.android.collect.Lists; @@ -31,6 +33,7 @@ import java.util.Iterator; import java.util.concurrent.Executor; import javax.inject.Inject; +import javax.inject.Named; /** * A Monitor for reporting a {@link CommunalSource} presence. @@ -42,7 +45,7 @@ public class CommunalSourceMonitor { // A list of {@link Callback} that have registered to receive updates. private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList(); - private final CommunalConditionsMonitor mConditionsMonitor; + private final Monitor mConditionsMonitor; private final Executor mExecutor; private CommunalSource mCurrentSource; @@ -53,7 +56,7 @@ public class CommunalSourceMonitor { // Whether the class is currently listening for condition changes. private boolean mListeningForConditions = false; - private final CommunalConditionsMonitor.Callback mConditionsCallback = + private final Monitor.Callback mConditionsCallback = allConditionsMet -> { if (mAllCommunalConditionsMet != allConditionsMet) { if (DEBUG) Log.d(TAG, "communal conditions changed: " + allConditionsMet); @@ -66,7 +69,7 @@ public class CommunalSourceMonitor { @VisibleForTesting @Inject public CommunalSourceMonitor(@Main Executor executor, - CommunalConditionsMonitor communalConditionsMonitor) { + @Named(COMMUNAL_CONDITIONS) Monitor communalConditionsMonitor) { mExecutor = executor; mConditionsMonitor = communalConditionsMonitor; } diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java index f27ae344eb24..e1f1ac42884d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java @@ -34,6 +34,8 @@ import com.android.systemui.idle.AmbientLightModeMonitor; import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm; import com.android.systemui.idle.dagger.IdleViewComponent; import com.android.systemui.util.condition.Condition; +import com.android.systemui.util.condition.Monitor; +import com.android.systemui.util.condition.dagger.MonitorComponent; import java.util.Collections; import java.util.HashSet; @@ -135,4 +137,14 @@ public interface CommunalModule { return Optional.empty(); } } + + /** */ + @Provides + @Named(COMMUNAL_CONDITIONS) + static Monitor provideCommunalSourceMonitor( + @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions, + MonitorComponent.Factory factory) { + final MonitorComponent component = factory.create(communalConditions, new HashSet<>()); + return component.getMonitor(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 2e0c0f45a960..e3376789c0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -33,6 +33,7 @@ import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; @@ -40,7 +41,6 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; -import com.android.wm.shell.sizecompatui.SizeCompatUI; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; @@ -112,7 +112,7 @@ public interface SysUIComponent { Builder setRecentTasks(Optional<RecentTasks> r); @BindsInstance - Builder setSizeCompatUI(Optional<SizeCompatUI> s); + Builder setCompatUI(Optional<CompatUI> s); @BindsInstance Builder setDragAndDrop(Optional<DragAndDrop> d); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 90a3ad225f51..b815d4e9884b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -25,6 +25,7 @@ import com.android.wm.shell.ShellInit; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.dagger.TvWMShellModule; import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; @@ -35,7 +36,6 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; -import com.android.wm.shell.sizecompatui.SizeCompatUI; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; @@ -119,7 +119,7 @@ public interface WMComponent { Optional<RecentTasks> getRecentTasks(); @WMSingleton - SizeCompatUI getSizeCompatUI(); + CompatUI getCompatUI(); @WMSingleton DragAndDrop getDragAndDrop(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 471a327ad987..b2fe3bb94dd3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -37,10 +37,14 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.unfold.FoldAodAnimationController; +import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus; +import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.WakeLock; import java.util.Calendar; +import java.util.Optional; import javax.inject.Inject; @@ -49,7 +53,8 @@ import javax.inject.Inject; */ @DozeScope public class DozeUi implements DozeMachine.Part, TunerService.Tunable, - ConfigurationController.ConfigurationListener, StatusBarStateController.StateListener { + ConfigurationController.ConfigurationListener, FoldAodAnimationStatus, + StatusBarStateController.StateListener { // if enabled, calls dozeTimeTick() whenever the time changes: private static final boolean BURN_IN_TESTING_ENABLED = false; private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min @@ -57,6 +62,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, private final DozeHost mHost; private final Handler mHandler; private final WakeLock mWakeLock; + private final FoldAodAnimationController mFoldAodAnimationController; private DozeMachine mMachine; private final AlarmTimeout mTimeTicker; private final boolean mCanAnimateTransition; @@ -100,6 +106,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor, DozeLog dozeLog, TunerService tunerService, StatusBarStateController statusBarStateController, + Optional<SysUIUnfoldComponent> sysUiUnfoldComponent, ConfigurationController configurationController) { mContext = context; mWakeLock = wakeLock; @@ -118,12 +125,23 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, mConfigurationController = configurationController; mConfigurationController.addCallback(this); + + mFoldAodAnimationController = sysUiUnfoldComponent + .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); + + if (mFoldAodAnimationController != null) { + mFoldAodAnimationController.addCallback(this); + } } @Override public void destroy() { mTunerService.removeTunable(this); mConfigurationController.removeCallback(this); + + if (mFoldAodAnimationController != null) { + mFoldAodAnimationController.removeCallback(this); + } } @Override @@ -142,7 +160,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, && (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff()) && !mHost.isPowerSaveActive(); mDozeParameters.setControlScreenOffAnimation(controlScreenOff); - mHost.setAnimateScreenOff(controlScreenOff); + mHost.setAnimateScreenOff(controlScreenOff + && mDozeParameters.shouldAnimateDozingChange()); } } @@ -299,4 +318,9 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, public void onStatePostChange() { updateAnimateScreenOff(); } + + @Override + public void onFoldToAodAnimationChanged() { + updateAnimateScreenOff(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java new file mode 100644 index 000000000000..bc1f772e14bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.constraintlayout.widget.ConstraintLayout; + +/** + * {@link DreamOverlayContainerView} contains a dream overlay and its status bar. + */ +public class DreamOverlayContainerView extends ConstraintLayout { + public DreamOverlayContainerView(Context context) { + this(context, null); + } + + public DreamOverlayContainerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DreamOverlayContainerView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public DreamOverlayContainerView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 8f0ea2fb2f87..20ccacc49fc6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -29,11 +29,11 @@ import android.view.WindowInsets; import android.view.WindowManager; import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.PhoneWindow; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.dagger.DreamOverlayComponent; import java.util.concurrent.Executor; @@ -54,10 +54,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private final Executor mExecutor; // The state controller informs the service of updates to the overlays present. private final DreamOverlayStateController mStateController; + // The component used to resolve dream overlay dependencies. + private final DreamOverlayComponent mDreamOverlayComponent; - // The window is populated once the dream informs the service it has begun dreaming. - private Window mWindow; - private ConstraintLayout mLayout; + // The dream overlay's content view, which is located below the status bar (in z-order) and is + // the space into which widgets are placed. + private ViewGroup mDreamOverlayContentView; private final DreamOverlayStateController.Callback mOverlayStateCallback = new DreamOverlayStateController.Callback() { @@ -90,12 +92,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ new ViewTreeObserver.OnComputeInternalInsetsListener() { @Override public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { - if (mLayout != null) { + if (mDreamOverlayContentView != null) { inoutInfo.setTouchableInsets( ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); final Region region = new Region(); - for (int i = 0; i < mLayout.getChildCount(); i++) { - View child = mLayout.getChildAt(i); + for (int i = 0; i < mDreamOverlayContentView.getChildCount(); i++) { + View child = mDreamOverlayContentView.getChildAt(i); final Rect rect = new Rect(); child.getGlobalVisibleRect(rect); region.op(rect, Region.Op.UNION); @@ -106,16 +108,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ } }; + @Inject + public DreamOverlayService( + Context context, + @Main Executor executor, + DreamOverlayStateController overlayStateController, + DreamOverlayComponent.Factory dreamOverlayComponentFactory) { + mContext = context; + mExecutor = executor; + mStateController = overlayStateController; + mDreamOverlayComponent = dreamOverlayComponentFactory.create(); + + mStateController.addCallback(mOverlayStateCallback); + } + @Override public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { mExecutor.execute(() -> addOverlayWindowLocked(layoutParams)); } private void reloadOverlaysLocked() { - if (mLayout == null) { + if (mDreamOverlayContentView == null) { return; } - mLayout.removeAllViews(); + mDreamOverlayContentView.removeAllViews(); for (OverlayProvider overlayProvider : mStateController.getOverlays()) { addOverlay(overlayProvider); } @@ -129,31 +145,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ * into the dream window. */ private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) { - mWindow = new PhoneWindow(mContext); - mWindow.setAttributes(layoutParams); - mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true); + final PhoneWindow window = new PhoneWindow(mContext); + window.setAttributes(layoutParams); + window.setWindowManager(null, layoutParams.token, "DreamOverlay", true); - mWindow.setBackgroundDrawable(new ColorDrawable(0)); + window.setBackgroundDrawable(new ColorDrawable(0)); - mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - mWindow.requestFeature(Window.FEATURE_NO_TITLE); + window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + window.requestFeature(Window.FEATURE_NO_TITLE); // Hide all insets when the dream is showing - mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars()); - mWindow.setDecorFitsSystemWindows(false); + window.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars()); + window.setDecorFitsSystemWindows(false); if (DEBUG) { Log.d(TAG, "adding overlay window to dream"); } - mLayout = new ConstraintLayout(mContext); - mLayout.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - mLayout.addOnAttachStateChangeListener(mRootViewAttachListener); - mWindow.setContentView(mLayout); + window.setContentView(mDreamOverlayComponent.getDreamOverlayContainerView()); + mDreamOverlayContentView = mDreamOverlayComponent.getDreamOverlayContentView(); + + mDreamOverlayComponent.getDreamOverlayStatusBarViewController().init(); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); - windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes()); + windowManager.addView(window.getDecorView(), window.getAttributes()); mExecutor.execute(this::reloadOverlaysLocked); } @@ -163,11 +178,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ (view, layoutParams) -> { // Always move UI related work to the main thread. mExecutor.execute(() -> { - if (mLayout == null) { + if (mDreamOverlayContentView == null) { return; } - mLayout.addView(view, layoutParams); + mDreamOverlayContentView.addView(view, layoutParams); }); }, () -> { @@ -178,15 +193,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ }); } - @Inject - public DreamOverlayService(Context context, @Main Executor executor, - DreamOverlayStateController overlayStateController) { - mContext = context; - mExecutor = executor; - mStateController = overlayStateController; - mStateController.addCallback(mOverlayStateCallback); - } - @Override public void onDestroy() { mStateController.removeCallback(mOverlayStateCallback); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java new file mode 100644 index 000000000000..9847ef633bc1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.android.internal.util.Preconditions; +import com.android.systemui.R; +import com.android.systemui.battery.BatteryMeterView; +import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; + +/** + * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a + * dream. The status bar includes status icons such as battery and wifi. + */ +public class DreamOverlayStatusBarView extends ConstraintLayout implements + BatteryStateChangeCallback { + + private BatteryMeterView mBatteryView; + private ImageView mWifiStatusView; + + public DreamOverlayStatusBarView(Context context) { + this(context, null); + } + + public DreamOverlayStatusBarView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public DreamOverlayStatusBarView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery), + "R.id.dream_overlay_battery must not be null"); + mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status), + "R.id.dream_overlay_wifi_status must not be null"); + + mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off)); + } + + /** + * Whether to show the battery percent text next to the battery status icons. + * @param show True if the battery percent text should be shown. + */ + void showBatteryPercentText(boolean show) { + mBatteryView.setForceShowPercent(show); + } + + /** + * Whether to show the wifi status icon. + * @param show True if the wifi status icon should be shown. + */ + void showWifiStatus(boolean show) { + // Only show the wifi status icon when wifi isn't available. + mWifiStatusView.setVisibility(show ? View.VISIBLE : View.GONE); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java new file mode 100644 index 000000000000..5674b9f3f9fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams; + +import android.annotation.IntDef; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; + +import com.android.systemui.battery.BatteryMeterViewController; +import com.android.systemui.dreams.dagger.DreamOverlayComponent; +import com.android.systemui.dreams.dagger.DreamOverlayModule; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.util.ViewController; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * View controller for {@link DreamOverlayStatusBarView}. + */ +@DreamOverlayComponent.DreamOverlayScope +public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> { + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "WIFI_STATUS_" }, value = { + WIFI_STATUS_UNKNOWN, + WIFI_STATUS_UNAVAILABLE, + WIFI_STATUS_AVAILABLE + }) + private @interface WifiStatus {} + private static final int WIFI_STATUS_UNKNOWN = 0; + private static final int WIFI_STATUS_UNAVAILABLE = 1; + private static final int WIFI_STATUS_AVAILABLE = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "BATTERY_STATUS_" }, value = { + BATTERY_STATUS_UNKNOWN, + BATTERY_STATUS_NOT_CHARGING, + BATTERY_STATUS_CHARGING + }) + private @interface BatteryStatus {} + private static final int BATTERY_STATUS_UNKNOWN = 0; + private static final int BATTERY_STATUS_NOT_CHARGING = 1; + private static final int BATTERY_STATUS_CHARGING = 2; + + private final BatteryController mBatteryController; + private final BatteryMeterViewController mBatteryMeterViewController; + private final ConnectivityManager mConnectivityManager; + private final boolean mShowPercentAvailable; + + private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); + + private final NetworkCallback mNetworkCallback = new NetworkCallback() { + @Override + public void onCapabilitiesChanged( + Network network, NetworkCapabilities networkCapabilities) { + onWifiAvailabilityChanged( + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)); + } + + @Override + public void onAvailable(Network network) { + onWifiAvailabilityChanged(true); + } + + @Override + public void onLost(Network network) { + onWifiAvailabilityChanged(false); + } + }; + + private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback = + new BatteryController.BatteryStateChangeCallback() { + @Override + public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { + DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging); + } + }; + + private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN; + private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN; + + @Inject + public DreamOverlayStatusBarViewController( + Context context, + DreamOverlayStatusBarView view, + BatteryController batteryController, + @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER) + BatteryMeterViewController batteryMeterViewController, + ConnectivityManager connectivityManager) { + super(view); + mBatteryController = batteryController; + mBatteryMeterViewController = batteryMeterViewController; + mConnectivityManager = connectivityManager; + + mShowPercentAvailable = context.getResources().getBoolean( + com.android.internal.R.bool.config_battery_percentage_setting_available); + } + + @Override + protected void onInit() { + super.onInit(); + mBatteryMeterViewController.init(); + } + + @Override + protected void onViewAttached() { + mBatteryController.addCallback(mBatteryStateChangeCallback); + mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback); + + NetworkCapabilities capabilities = + mConnectivityManager.getNetworkCapabilities( + mConnectivityManager.getActiveNetwork()); + onWifiAvailabilityChanged( + capabilities != null + && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)); + } + + @Override + protected void onViewDetached() { + mBatteryController.removeCallback(mBatteryStateChangeCallback); + mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); + } + + /** + * Wifi availability has changed. Update the wifi status icon as appropriate. + * @param available Whether wifi is available. + */ + private void onWifiAvailabilityChanged(boolean available) { + final int newWifiStatus = available ? WIFI_STATUS_AVAILABLE : WIFI_STATUS_UNAVAILABLE; + if (mWifiStatus != newWifiStatus) { + mWifiStatus = newWifiStatus; + mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE); + } + } + + /** + * The battery level has changed. Update the battery status icon as appropriate. + * @param charging Whether the battery is currently charging. + */ + private void onBatteryLevelChanged(boolean charging) { + final int newBatteryStatus = + charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING; + if (mBatteryStatus != newBatteryStatus) { + mBatteryStatus = newBatteryStatus; + mView.showBatteryPercentText( + mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 7bf2361e471c..ff5beb54bd89 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -21,8 +21,6 @@ import dagger.Module; /** * Dagger Module providing Communal-related functionality. */ -@Module(subcomponents = { - AppWidgetOverlayComponent.class, -}) +@Module(subcomponents = {AppWidgetOverlayComponent.class, DreamOverlayComponent.class}) public interface DreamModule { } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java new file mode 100644 index 000000000000..a3a446a0dbca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.view.ViewGroup; + +import com.android.systemui.dreams.DreamOverlayContainerView; +import com.android.systemui.dreams.DreamOverlayStatusBarViewController; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +import dagger.Subcomponent; + +/** + * Dagger subcomponent for {@link DreamOverlayModule}. + */ +@Subcomponent(modules = {DreamOverlayModule.class}) +@DreamOverlayComponent.DreamOverlayScope +public interface DreamOverlayComponent { + /** Simple factory for {@link DreamOverlayComponent}. */ + @Subcomponent.Factory + interface Factory { + DreamOverlayComponent create(); + } + + /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */ + @Documented + @Retention(RUNTIME) + @Scope + @interface DreamOverlayScope {} + + /** Builds a {@link DreamOverlayContainerView} */ + @DreamOverlayScope + DreamOverlayContainerView getDreamOverlayContainerView(); + + /** Builds a content view for dream overlays */ + @DreamOverlayScope + ViewGroup getDreamOverlayContentView(); + + /** Builds a {@link DreamOverlayStatusBarViewController}. */ + @DreamOverlayScope + DreamOverlayStatusBarViewController getDreamOverlayStatusBarViewController(); +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java new file mode 100644 index 000000000000..d0a8fad9b1e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.dagger; + +import android.content.ContentResolver; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import com.android.internal.util.Preconditions; +import com.android.systemui.R; +import com.android.systemui.battery.BatteryMeterView; +import com.android.systemui.battery.BatteryMeterViewController; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.DreamOverlayContainerView; +import com.android.systemui.dreams.DreamOverlayStatusBarView; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.tuner.TunerService; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +/** Dagger module for {@link DreamOverlayComponent}. */ +@Module +public abstract class DreamOverlayModule { + private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view"; + public static final String DREAM_OVERLAY_BATTERY_CONTROLLER = + "dream_overlay_battery_controller"; + + /** */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + public static DreamOverlayContainerView providesDreamOverlayContainerView( + LayoutInflater layoutInflater) { + return Preconditions.checkNotNull((DreamOverlayContainerView) + layoutInflater.inflate(R.layout.dream_overlay_container, null), + "R.layout.dream_layout_container could not be properly inflated"); + } + + /** */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + public static ViewGroup providesDreamOverlayContentView(DreamOverlayContainerView view) { + return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_content), + "R.id.dream_overlay_content must not be null"); + } + + /** */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView( + DreamOverlayContainerView view) { + return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar), + "R.id.status_bar must not be null"); + } + + /** */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + @Named(DREAM_OVERLAY_BATTERY_VIEW) + static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) { + return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery), + "R.id.battery must not be null"); + } + + /** */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + @Named(DREAM_OVERLAY_BATTERY_CONTROLLER) + static BatteryMeterViewController providesBatteryMeterViewController( + @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView, + ConfigurationController configurationController, + TunerService tunerService, + BroadcastDispatcher broadcastDispatcher, + @Main Handler mainHandler, + ContentResolver contentResolver, + BatteryController batteryController) { + return new BatteryMeterViewController( + batteryMeterView, + configurationController, + tunerService, + broadcastDispatcher, + mainHandler, + contentResolver, + batteryController); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c592431d360e..0c9e3157f4d1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -122,6 +122,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.util.DeviceConfigProxy; @@ -131,7 +132,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Optional; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicInteger; import dagger.Lazy; @@ -439,7 +439,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private boolean mInGestureNavigationMode; private boolean mWakeAndUnlocking; - private IKeyguardDrawnCallback mDrawnCallback; + private Runnable mWakeAndUnlockingDrawnCallback; private CharSequence mCustomMessage; /** @@ -817,7 +817,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private DozeParameters mDozeParameters; private final Optional<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation; - private final AtomicInteger mPendingDrawnTasks = new AtomicInteger(); + private final Optional<FoldAodAnimationController> mFoldAodAnimationController; + private final PendingDrawnTasksContainer mPendingDrawnTasks = new PendingDrawnTasksContainer(); private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; @@ -877,8 +878,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode); })); mDozeParameters = dozeParameters; - mUnfoldLightRevealAnimation = unfoldComponent.map( - c -> c.getUnfoldLightRevealOverlayAnimation()); + + mUnfoldLightRevealAnimation = unfoldComponent + .map(SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation); + mFoldAodAnimationController = unfoldComponent + .map(SysUIUnfoldComponent::getFoldAodAnimationController); + mStatusBarStateController = statusBarStateController; statusBarStateController.addCallback(this); @@ -1069,7 +1074,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mDeviceInteractive = false; mGoingToSleep = false; mWakeAndUnlocking = false; - mAnimatingScreenOff = mDozeParameters.shouldControlUnlockedScreenOff(); + mAnimatingScreenOff = mDozeParameters.shouldAnimateDozingChange(); resetKeyguardDonePendingLocked(); mHideAnimationRun = false; @@ -2230,14 +2235,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner; mKeyguardExitAnimationRunner = null; - if (mWakeAndUnlocking && mDrawnCallback != null) { + if (mWakeAndUnlocking && mWakeAndUnlockingDrawnCallback != null) { // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report // the next draw from here so we don't have to wait for window manager to signal // this to our ViewRootImpl. mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw(); - notifyDrawn(mDrawnCallback); - mDrawnCallback = null; + mWakeAndUnlockingDrawnCallback.run(); + mWakeAndUnlockingDrawnCallback = null; } LatencyTracker.getInstance(mContext) @@ -2573,31 +2578,27 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn"); - if (mUnfoldLightRevealAnimation.isPresent()) { - mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn + mPendingDrawnTasks.reset(); + if (mUnfoldLightRevealAnimation.isPresent()) { mUnfoldLightRevealAnimation.get() - .onScreenTurningOn(() -> { - if (mPendingDrawnTasks.decrementAndGet() == 0) { - try { - callback.onDrawn(); - } catch (RemoteException e) { - Slog.w(TAG, "Exception calling onDrawn():", e); - } - } - }); - } else { - mPendingDrawnTasks.set(1); // only keyguard drawn + .onScreenTurningOn(mPendingDrawnTasks.registerTask("unfold-reveal")); + } + + if (mFoldAodAnimationController.isPresent()) { + mFoldAodAnimationController.get() + .onScreenTurningOn(mPendingDrawnTasks.registerTask("fold-to-aod")); } mKeyguardViewControllerLazy.get().onScreenTurningOn(); if (callback != null) { if (mWakeAndUnlocking) { - mDrawnCallback = callback; - } else { - notifyDrawn(callback); + mWakeAndUnlockingDrawnCallback = + mPendingDrawnTasks.registerTask("wake-and-unlocking"); } } + + mPendingDrawnTasks.onTasksComplete(() -> notifyDrawn(callback)); } Trace.endSection(); } @@ -2606,6 +2607,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn"); synchronized (this) { if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn"); + + mPendingDrawnTasks.reset(); mKeyguardViewControllerLazy.get().onScreenTurnedOn(); } Trace.endSection(); @@ -2614,18 +2617,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private void handleNotifyScreenTurnedOff() { synchronized (this) { if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOff"); - mDrawnCallback = null; + mWakeAndUnlockingDrawnCallback = null; } } private void notifyDrawn(final IKeyguardDrawnCallback callback) { Trace.beginSection("KeyguardViewMediator#notifyDrawn"); - if (mPendingDrawnTasks.decrementAndGet() == 0) { - try { + try { + if (callback != null) { callback.onDrawn(); - } catch (RemoteException e) { - Slog.w(TAG, "Exception calling onDrawn():", e); } + } catch (RemoteException e) { + Slog.w(TAG, "Exception calling onDrawn():", e); } Trace.endSection(); } @@ -2784,9 +2787,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun); pw.print(" mPendingReset: "); pw.println(mPendingReset); pw.print(" mPendingLock: "); pw.println(mPendingLock); - pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get()); + pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.getPendingCount()); pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking); - pw.print(" mDrawnCallback: "); pw.println(mDrawnCallback); + pw.print(" mDrawnCallback: "); pw.println(mWakeAndUnlockingDrawnCallback); } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt new file mode 100644 index 000000000000..bccd106db836 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard + +import android.os.Trace +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference + +/** + * Allows to wait for multiple callbacks and notify when the last one is executed + */ +class PendingDrawnTasksContainer { + + private lateinit var pendingDrawnTasksCount: AtomicInteger + private var completionCallback: AtomicReference<Runnable> = AtomicReference() + + /** + * Registers a task that we should wait for + * @return a runnable that should be invoked when the task is finished + */ + fun registerTask(name: String): Runnable { + pendingDrawnTasksCount.incrementAndGet() + + if (ENABLE_TRACE) { + Trace.beginAsyncSection("PendingDrawnTasksContainer#$name", 0) + } + + return Runnable { + if (pendingDrawnTasksCount.decrementAndGet() == 0) { + val onComplete = completionCallback.getAndSet(null) + onComplete?.run() + + if (ENABLE_TRACE) { + Trace.endAsyncSection("PendingDrawnTasksContainer#$name", 0) + } + } + } + } + + /** + * Clears state and initializes the container + */ + fun reset() { + // Create new objects in case if there are pending callbacks from the previous invocations + completionCallback = AtomicReference() + pendingDrawnTasksCount = AtomicInteger(0) + } + + /** + * Starts waiting for all tasks to be completed + * When all registered tasks complete it will invoke the [onComplete] callback + */ + fun onTasksComplete(onComplete: Runnable) { + completionCallback.set(onComplete) + + if (pendingDrawnTasksCount.get() == 0) { + val currentOnComplete = completionCallback.getAndSet(null) + currentOnComplete?.run() + } + } + + /** + * Returns current pending tasks count + */ + fun getPendingCount(): Int = pendingDrawnTasksCount.get() +} + +private const val ENABLE_TRACE = false diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 8026df747ff6..cc91384d93b8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -1251,6 +1251,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private void onImeSwitcherClick(View v) { mInputMethodManager.showInputMethodPickerFromSystem( true /* showAuxiliarySubtypes */, mDisplayId); + mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP); }; private boolean onLongPressBackHome(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java index debd2ebb51be..d27b71673ce5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java @@ -96,6 +96,9 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @UiEvent(doc = "The overview button was pressed in the navigation bar.") NAVBAR_OVERVIEW_BUTTON_TAP(535), + @UiEvent(doc = "The ime switcher button was pressed in the navigation bar.") + NAVBAR_IME_SWITCHER_BUTTON_TAP(923), + @UiEvent(doc = "The home button was long-pressed in the navigation bar.") NAVBAR_HOME_BUTTON_LONGPRESS(536), diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 5bd02cc207b9..10efec309971 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -48,6 +48,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; @@ -562,7 +563,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener return this; } - CustomTile build() { + @VisibleForTesting + public CustomTile build() { if (mUserContext == null) { throw new NullPointerException("UserContext cannot be null"); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index ac95bf50bb98..c2a9e3adb0b8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -169,7 +169,7 @@ public class QSFactoryImpl implements QSFactory { mFgsManagerTileProvider = fgsManagerTileProvider; } - public QSTile createTile(String tileSpec) { + public final QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { tile.initialize(); @@ -178,7 +178,7 @@ public class QSFactoryImpl implements QSFactory { return tile; } - private QSTileImpl createTileInternal(String tileSpec) { + protected QSTileImpl createTileInternal(String tileSpec) { // Stock tiles. switch (tileSpec) { case "wifi": diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt index 75cf4d1ace8e..939a29711f45 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt @@ -54,7 +54,7 @@ class FgsManagerTile @Inject constructor( qsLogger: QSLogger?, private val fgsManagerDialogFactory: FgsManagerDialogFactory, private val runningFgsController: RunningFgsController -) : QSTileImpl<QSTile.State?>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, +) : QSTileImpl<QSTile.State>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger), RunningFgsController.Callback { override fun handleInitialize() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 7dd24b48ace2..dd742b86a483 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -90,6 +90,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; @@ -485,6 +486,11 @@ public class InternetDialogController implements AccessPointController.AccessPoi private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) { class DisplayInfo { + DisplayInfo(SubscriptionInfo subscriptionInfo, CharSequence originalName) { + this.subscriptionInfo = subscriptionInfo; + this.originalName = originalName; + } + public SubscriptionInfo subscriptionInfo; public CharSequence originalName; public CharSequence uniqueName; @@ -498,12 +504,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi // Filter out null values. return (i != null && i.getDisplayName() != null); }) - .map(i -> { - DisplayInfo info = new DisplayInfo(); - info.subscriptionInfo = i; - info.originalName = i.getDisplayName().toString().trim(); - return info; - }); + .map(i -> new DisplayInfo(i, i.getDisplayName().toString().trim())); // A Unique set of display names Set<CharSequence> uniqueNames = new HashSet<>(); @@ -582,7 +583,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi return ""; } - int resId = mapIconSets(config).get(iconKey).dataContentDescription; + int resId = Objects.requireNonNull(mapIconSets(config).get(iconKey)).dataContentDescription; if (isCarrierNetworkActive()) { SignalIcon.MobileIconGroup carrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI; @@ -878,10 +879,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi if (accessPoints == null || accessPoints.size() == 0) { mConnectedEntry = null; mWifiEntriesCount = 0; - if (mCallback != null) { - mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */, - false /* hasMoreEntry */); - } + mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */, + false /* hasMoreEntry */); return; } @@ -913,9 +912,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi mConnectedEntry = connectedEntry; mWifiEntriesCount = wifiEntries.size(); - if (mCallback != null) { - mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry); - } + mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 3ed7e84af020..e7cd1e2dab3c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -76,6 +76,7 @@ import androidx.annotation.NonNull; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.Dumpable; @@ -88,6 +89,7 @@ import com.android.systemui.navigationbar.NavigationBar; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.recents.IOverviewProxy; @@ -161,6 +163,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final Optional<StartingSurface> mStartingSurface; private final SmartspaceTransitionController mSmartspaceTransitionController; private final Optional<RecentTasks> mRecentTasks; + private final UiEventLogger mUiEventLogger; private Region mActiveNavBarRegion; @@ -248,6 +251,7 @@ public class OverviewProxyService extends CurrentUserTracker implements mContext.getSystemService(InputMethodManager.class) .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */, DEFAULT_DISPLAY); + mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP); } @Override @@ -560,6 +564,7 @@ public class OverviewProxyService extends CurrentUserTracker implements ShellTransitions shellTransitions, ScreenLifecycle screenLifecycle, SmartspaceTransitionController smartspaceTransitionController, + UiEventLogger uiEventLogger, DumpManager dumpManager) { super(broadcastDispatcher); mContext = context; @@ -581,6 +586,7 @@ public class OverviewProxyService extends CurrentUserTracker implements mOneHandedOptional = oneHandedOptional; mShellTransitions = shellTransitions; mRecentTasks = recentTasks; + mUiEventLogger = uiEventLogger; // Assumes device always starts with back button until launcher tells it that it does not mNavBarButtonAlpha = 1.0f; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index ce86953177e5..962c7fa6aea7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -102,12 +102,17 @@ class PrivacyDotViewController @Inject constructor( configurationController.addCallback(object : ConfigurationController.ConfigurationListener { override fun onLayoutDirectionChanged(isRtl: Boolean) { - synchronized(this) { - val corner = selectDesignatedCorner(nextViewState.rotation, isRtl) - nextViewState = nextViewState.copy( - layoutRtl = isRtl, - designatedCorner = corner - ) + uiExecutor?.execute { + // If rtl changed, hide all dotes until the next state resolves + setCornerVisibilities(View.INVISIBLE) + + synchronized(this) { + val corner = selectDesignatedCorner(nextViewState.rotation, isRtl) + nextViewState = nextViewState.copy( + layoutRtl = isRtl, + designatedCorner = corner + ) + } } } }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt index f8d6c6d8ec10..b2e15f48004c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt @@ -25,8 +25,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.tuner.TunerService @@ -44,7 +44,7 @@ class BypassHeadsUpNotifier @Inject constructor( private val headsUpManager: HeadsUpManagerPhone, private val notificationLockscreenUserManager: NotificationLockscreenUserManager, private val mediaManager: NotificationMediaManager, - private val entryManager: NotificationEntryManager, + private val commonNotifCollection: CommonNotifCollection, tunerService: TunerService ) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener { @@ -77,8 +77,7 @@ class BypassHeadsUpNotifier @Inject constructor( override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) { val previous = currentMediaEntry - var newEntry = entryManager - .getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey) + var newEntry = commonNotifCollection.getEntry(mediaManager.mediaNotificationKey) if (!NotificationMediaManager.isPlayingState(state)) { newEntry = null } @@ -112,7 +111,7 @@ class BypassHeadsUpNotifier @Inject constructor( // filter notifications invisible on Keyguard return false } - if (entryManager.getActiveNotificationUnfiltered(entry.key) != null) { + if (commonNotifCollection.getEntry(entry.key) != null) { // filter notifications not the active list currently return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index e3a4bf0170fb..2dc92764a4af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -51,6 +51,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400; public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400; + public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600; public static final int ANIMATION_DURATION_PULSE_APPEAR = KeyguardSliceView.DEFAULT_ANIM_DURATION; public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 4b8b580eba0b..d87a02493a23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -243,6 +243,10 @@ public class DozeParameters implements return mScreenOffAnimationController.shouldShowLightRevealScrim(); } + public boolean shouldAnimateDozingChange() { + return mScreenOffAnimationController.shouldAnimateDozingChange(); + } + /** * Whether we're capable of controlling the screen off animation if we want to. This isn't * possible if AOD isn't even enabled or if the flag is disabled. @@ -324,6 +328,7 @@ public class DozeParameters implements for (Callback callback : mCallbacks) { callback.onAlwaysOnChange(); } + mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 097c0d1677f9..3b7063e6662f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -26,6 +26,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.TOP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; +import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; @@ -34,6 +35,7 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; +import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING; @@ -1423,11 +1425,12 @@ public class NotificationPanelViewController extends PanelViewController { .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia(); boolean splitShadeWithActiveMedia = mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia(); + boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange(); if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade) || (splitShadeWithActiveMedia && !mDozing)) { - mKeyguardStatusViewController.displayClock(SMALL); + mKeyguardStatusViewController.displayClock(SMALL, shouldAnimateClockChange); } else { - mKeyguardStatusViewController.displayClock(LARGE); + mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange); } updateKeyguardStatusViewAlignment(true /* animate */); int userIconHeight = mKeyguardQsUserSwitchController != null @@ -1463,7 +1466,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController.isClockTopAligned()); mClockPositionAlgorithm.run(mClockPositionResult); boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); - boolean animateClock = animate || mAnimateNextPositionUpdate; + boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockY, mClockPositionResult.clockScale, animateClock); @@ -3821,6 +3824,45 @@ public class NotificationPanelViewController extends PanelViewController { } } + /** + * Updates the views to the initial state for the fold to AOD animation + */ + public void prepareFoldToAodAnimation() { + // Force show AOD UI even if we are not locked + showAodUi(); + + // Move the content of the AOD all the way to the left + // so we can animate to the initial position + final int translationAmount = mView.getResources().getDimensionPixelSize( + R.dimen.below_clock_padding_start); + mView.setTranslationX(-translationAmount); + mView.setAlpha(0); + } + + /** + * Starts fold to AOD animation + */ + public void startFoldToAodAnimation(Runnable endAction) { + mView.animate() + .translationX(0) + .alpha(1f) + .setDuration(ANIMATION_DURATION_FOLD_TO_AOD) + .setInterpolator(EMPHASIZED_DECELERATE) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + endAction.run(); + } + @Override + public void onAnimationEnd(Animator animation) { + endAction.run(); + } + }) + .start(); + + mKeyguardStatusViewController.animateFoldToAod(); + } + /** */ public void setImportantForAccessibility(int mode) { mView.setImportantForAccessibility(mode); @@ -3935,6 +3977,10 @@ public class NotificationPanelViewController extends PanelViewController { mView.setAlpha(alpha); } + public void resetTranslation() { + mView.setTranslationX(0f); + } + public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) { return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration( durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index f67d18183c10..1e71ceb8cae8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -38,7 +38,6 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.window.StatusBarWindowView; import com.android.systemui.util.leak.RotationUtils; import java.util.Objects; @@ -76,6 +75,7 @@ public class PhoneStatusBarView extends FrameLayout { @Override public void onFinishInflate() { + super.onFinishInflate(); mBattery = findViewById(R.id.battery); mClock = findViewById(R.id.clock); mCutoutSpace = findViewById(R.id.cutout_space_view); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt index 497e7d78ac4e..e806ca0d9005 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt @@ -19,16 +19,23 @@ import android.view.View import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.unfold.FoldAodAnimationController +import com.android.systemui.unfold.SysUIUnfoldComponent +import java.util.Optional import javax.inject.Inject @SysUISingleton class ScreenOffAnimationController @Inject constructor( + sysUiUnfoldComponent: Optional<SysUIUnfoldComponent>, unlockedScreenOffAnimation: UnlockedScreenOffAnimationController, private val wakefulnessLifecycle: WakefulnessLifecycle, ) : WakefulnessLifecycle.Observer { - // TODO(b/202844967) add fold to aod animation here - private val animations: List<ScreenOffAnimation> = listOf(unlockedScreenOffAnimation) + private val foldToAodAnimation: FoldAodAnimationController? = sysUiUnfoldComponent + .orElse(null)?.getFoldAodAnimationController() + + private val animations: List<ScreenOffAnimation> = + listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation) fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) { animations.forEach { it.initialize(statusBar, lightRevealScrim) } @@ -43,6 +50,19 @@ class ScreenOffAnimationController @Inject constructor( } /** + * Called when opaqueness of the light reveal scrim has change + * When [isOpaque] is true then scrim is visible and covers the screen + */ + fun onScrimOpaqueChanged(isOpaque: Boolean) = + animations.forEach { it.onScrimOpaqueChanged(isOpaque) } + + /** + * Called when always on display setting changed + */ + fun onAlwaysOnChanged(alwaysOn: Boolean) = + animations.forEach { it.onAlwaysOnChanged(alwaysOn) } + + /** * If returns true we are taking over the screen off animation from display manager to SysUI. * We can play our custom animation instead of default fade out animation. */ @@ -103,6 +123,12 @@ class ScreenOffAnimationController @Inject constructor( animations.any { it.isAnimationPlaying() } /** + * Return true to ignore requests to hide keyguard + */ + fun isKeyguardHideDelayed(): Boolean = + animations.any { it.isKeyguardHideDelayed() } + + /** * Return true to make the StatusBar expanded so we can animate [LightRevealScrim] */ fun shouldShowLightRevealScrim(): Boolean = @@ -145,6 +171,19 @@ class ScreenOffAnimationController @Inject constructor( */ fun shouldAnimateAodIcons(): Boolean = animations.all { it.shouldAnimateAodIcons() } + + /** + * Return true to animate doze state change, if returns false dozing will be applied without + * animation (sends only 0.0f or 1.0f dozing progress) + */ + fun shouldAnimateDozingChange(): Boolean = + animations.all { it.shouldAnimateDozingChange() } + + /** + * Return true to animate large <-> small clock transition + */ + fun shouldAnimateClockChange(): Boolean = + animations.all { it.shouldAnimateClockChange() } } interface ScreenOffAnimation { @@ -158,11 +197,17 @@ interface ScreenOffAnimation { fun shouldPlayAnimation(): Boolean = false fun isAnimationPlaying(): Boolean = false + fun onScrimOpaqueChanged(isOpaque: Boolean) {} + fun onAlwaysOnChanged(alwaysOn: Boolean) {} + fun shouldAnimateInKeyguard(): Boolean = false fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run() + fun isKeyguardHideDelayed(): Boolean = false fun shouldHideScrimOnWakeUp(): Boolean = false fun overrideNotificationsDozeAmount(): Boolean = false fun shouldShowAodIconsWhenShade(): Boolean = false fun shouldAnimateAodIcons(): Boolean = true + fun shouldAnimateDozingChange(): Boolean = true + fun shouldAnimateClockChange(): Boolean = true } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt index f6e19bff8f9b..8cf7288c9cd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt @@ -49,8 +49,9 @@ class SplitShadeHeaderController @Inject constructor( } private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS) - // TODO(b/194178072) Handle RSSI hiding when multi carrier private val iconManager: StatusBarIconController.TintedIconManager + private val iconContainer: StatusIconContainer + private val carrierIconSlots: List<String> private val qsCarrierGroupController: QSCarrierGroupController private var visible = false set(value) { @@ -117,10 +118,19 @@ class SplitShadeHeaderController @Inject constructor( batteryMeterViewController.ignoreTunerUpdates() batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) - val iconContainer: StatusIconContainer = statusBar.findViewById(R.id.statusIcons) + iconContainer = statusBar.findViewById(R.id.statusIcons) iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags) iconManager.setTint(Utils.getColorAttrDefaultColor(statusBar.context, android.R.attr.textColorPrimary)) + + carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) { + listOf( + statusBar.context.getString(com.android.internal.R.string.status_bar_no_calling), + statusBar.context.getString(com.android.internal.R.string.status_bar_call_strength) + ) + } else { + listOf(statusBar.context.getString(com.android.internal.R.string.status_bar_mobile)) + } qsCarrierGroupController = qsCarrierGroupControllerBuilder .setQSCarrierGroup(statusBar.findViewById(R.id.carrier_group)) .build() @@ -185,9 +195,20 @@ class SplitShadeHeaderController @Inject constructor( private fun updateListeners() { qsCarrierGroupController.setListening(visible) if (visible) { + updateSingleCarrier(qsCarrierGroupController.isSingleCarrier) + qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) } statusBarIconController.addIconGroup(iconManager) } else { + qsCarrierGroupController.setOnSingleCarrierChangedListener(null) statusBarIconController.removeIconGroup(iconManager) } } + + private fun updateSingleCarrier(singleCarrier: Boolean) { + if (singleCarrier) { + iconContainer.removeIgnoredSlots(carrierIconSlots) + } else { + iconContainer.addIgnoredSlots(carrierIconSlots) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 6c0b717fb85c..331299628f61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -341,7 +341,7 @@ public class StatusBar extends CoreStartable implements mStatusBarWindowState = state; mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN; mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden); - if (getStatusBarView() != null) { + if (mStatusBarView != null) { // Should #updateHideIconsForBouncer always be called, regardless of whether we have a // status bar view? If so, we can make #updateHideIconsForBouncer private. mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false); @@ -1124,23 +1124,18 @@ public class StatusBar extends CoreStartable implements // Set up CollapsedStatusBarFragment and PhoneStatusBarView StatusBarInitializer initializer = mStatusBarComponent.getStatusBarInitializer(); initializer.setStatusBarViewUpdatedListener( - new StatusBarInitializer.OnStatusBarViewUpdatedListener() { - @Override - public void onStatusBarViewUpdated( - @NonNull PhoneStatusBarView statusBarView, - @NonNull PhoneStatusBarViewController statusBarViewController) { - mStatusBarView = statusBarView; - mPhoneStatusBarViewController = statusBarViewController; - mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView); - // Ensure we re-propagate panel expansion values to the panel controller and - // any listeners it may have, such as PanelBar. This will also ensure we - // re-display the notification panel if necessary (for example, if - // a heads-up notification was being displayed and should continue being - // displayed). - mNotificationPanelViewController.updatePanelExpansionAndVisibility(); - setBouncerShowingForStatusBarComponents(mBouncerShowing); - checkBarModes(); - } + (statusBarView, statusBarViewController) -> { + mStatusBarView = statusBarView; + mPhoneStatusBarViewController = statusBarViewController; + mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView); + // Ensure we re-propagate panel expansion values to the panel controller and + // any listeners it may have, such as PanelBar. This will also ensure we + // re-display the notification panel if necessary (for example, if + // a heads-up notification was being displayed and should continue being + // displayed). + mNotificationPanelViewController.updatePanelExpansionAndVisibility(); + setBouncerShowingForStatusBarComponents(mBouncerShowing); + checkBarModes(); }); initializer.initializeStatusBar(mStatusBarComponent); @@ -1199,6 +1194,8 @@ public class StatusBar extends CoreStartable implements Runnable updateOpaqueness = () -> { mNotificationShadeWindowController.setLightRevealScrimOpaque( mLightRevealScrim.isScrimOpaque()); + mScreenOffAnimationController + .onScrimOpaqueChanged(mLightRevealScrim.isScrimOpaque()); }; if (opaque) { // Delay making the view opaque for a frame, because it needs some time to render @@ -1578,10 +1575,6 @@ public class StatusBar extends CoreStartable implements Trace.endSection(); } - protected PhoneStatusBarView getStatusBarView() { - return mStatusBarView; - } - public NotificationShadeWindowView getNotificationShadeWindowView() { return mNotificationShadeWindowView; } @@ -2915,7 +2908,17 @@ public class StatusBar extends CoreStartable implements showKeyguardImpl(); } } else { - return hideKeyguardImpl(force); + // During folding a foldable device this might be called as a result of + // 'onScreenTurnedOff' call for the inner display. + // In this case: + // * When phone is locked on folding: it doesn't make sense to hide keyguard as it + // will be immediately locked again + // * When phone is unlocked: we still don't want to execute hiding of the keyguard + // as the animation could prepare 'fake AOD' interface (without actually + // transitioning to keyguard state) and this might reset the view states + if (!mScreenOffAnimationController.isKeyguardHideDelayed()) { + return hideKeyguardImpl(force); + } } return false; } @@ -3078,6 +3081,7 @@ public class StatusBar extends CoreStartable implements mNotificationPanelViewController.onAffordanceLaunchEnded(); mNotificationPanelViewController.cancelAnimation(); mNotificationPanelViewController.setAlpha(1f); + mNotificationPanelViewController.resetTranslation(); mNotificationPanelViewController.resetViewGroupFade(); updateDozingState(); updateScrimController(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index b84e6e6f37cc..a4aeae9c0307 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -111,8 +111,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final NavigationModeController mNavigationModeController; private final NotificationShadeWindowController mNotificationShadeWindowController; private final KeyguardBouncer.Factory mKeyguardBouncerFactory; - private final WakefulnessLifecycle mWakefulnessLifecycle; - private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; private KeyguardMessageAreaController mKeyguardMessageAreaController; private final Lazy<ShadeController> mShadeController; @@ -244,8 +242,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb KeyguardStateController keyguardStateController, NotificationMediaManager notificationMediaManager, KeyguardBouncer.Factory keyguardBouncerFactory, - WakefulnessLifecycle wakefulnessLifecycle, - UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, KeyguardMessageAreaController.Factory keyguardMessageAreaFactory, Lazy<ShadeController> shadeController) { mContext = context; @@ -260,8 +256,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBarStateController = sysuiStatusBarStateController; mDockManager = dockManager; mKeyguardBouncerFactory = keyguardBouncerFactory; - mWakefulnessLifecycle = wakefulnessLifecycle; - mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mKeyguardMessageAreaFactory = keyguardMessageAreaFactory; mShadeController = shadeController; } @@ -1155,7 +1149,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public ViewRootImpl getViewRootImpl() { - return mStatusBar.getStatusBarView().getViewRootImpl(); + return mNotificationShadeWindowController.getNotificationShadeView().getViewRootImpl(); } public void launchPendingWakeupAction() { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt new file mode 100644 index 000000000000..fb9df01a0251 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.unfold + +import android.os.PowerManager +import android.provider.Settings +import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.keyguard.ScreenLifecycle +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.statusbar.phone.ScreenOffAnimation +import com.android.systemui.statusbar.phone.StatusBar +import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus +import com.android.systemui.util.settings.GlobalSettings +import dagger.Lazy +import javax.inject.Inject + +/** + * Controls folding to AOD animation: when AOD is enabled and foldable device is folded + * we play a special AOD animation on the outer screen + */ +@SysUIUnfoldScope +class FoldAodAnimationController @Inject constructor( + private val screenLifecycle: ScreenLifecycle, + private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, + private val wakefulnessLifecycle: WakefulnessLifecycle, + private val globalSettings: GlobalSettings +) : ScreenLifecycle.Observer, + CallbackController<FoldAodAnimationStatus>, + ScreenOffAnimation, + WakefulnessLifecycle.Observer { + + private var alwaysOnEnabled: Boolean = false + private var isScrimOpaque: Boolean = false + private lateinit var statusBar: StatusBar + private var pendingScrimReadyCallback: Runnable? = null + + private var shouldPlayAnimation = false + private val statusListeners = arrayListOf<FoldAodAnimationStatus>() + + private var isAnimationPlaying = false + + override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) { + this.statusBar = statusBar + + screenLifecycle.addObserver(this) + wakefulnessLifecycle.addObserver(this) + } + + /** + * Returns true if we should run fold to AOD animation + */ + override fun shouldPlayAnimation(): Boolean = + shouldPlayAnimation + + override fun startAnimation(): Boolean = + if (alwaysOnEnabled && + wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD && + globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0" + ) { + shouldPlayAnimation = true + + isAnimationPlaying = true + statusBar.notificationPanelViewController.prepareFoldToAodAnimation() + + statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged) + + true + } else { + shouldPlayAnimation = false + false + } + + override fun onStartedWakingUp() { + shouldPlayAnimation = false + isAnimationPlaying = false + } + + /** + * Called when screen starts turning on, the contents of the screen might not be visible yet. + * This method reports back that the animation is ready in [onReady] callback. + * + * @param onReady callback when the animation is ready + * @see [com.android.systemui.keyguard.KeyguardViewMediator] + */ + fun onScreenTurningOn(onReady: Runnable) { + if (shouldPlayAnimation) { + if (isScrimOpaque) { + onReady.run() + } else { + pendingScrimReadyCallback = onReady + } + } else { + // No animation, call ready callback immediately + onReady.run() + } + } + + /** + * Called when keyguard scrim opaque changed + */ + override fun onScrimOpaqueChanged(isOpaque: Boolean) { + isScrimOpaque = isOpaque + + if (isOpaque) { + pendingScrimReadyCallback?.run() + pendingScrimReadyCallback = null + } + } + + override fun onScreenTurnedOn() { + if (shouldPlayAnimation) { + statusBar.notificationPanelViewController.startFoldToAodAnimation { + // End action + isAnimationPlaying = false + keyguardViewMediatorLazy.get().maybeHandlePendingLock() + } + shouldPlayAnimation = false + } + } + + override fun isAnimationPlaying(): Boolean = + isAnimationPlaying + + override fun isKeyguardHideDelayed(): Boolean = + isAnimationPlaying() + + override fun shouldShowAodIconsWhenShade(): Boolean = + shouldPlayAnimation() + + override fun shouldAnimateAodIcons(): Boolean = + !shouldPlayAnimation() + + override fun shouldAnimateDozingChange(): Boolean = + !shouldPlayAnimation() + + override fun shouldAnimateClockChange(): Boolean = + !isAnimationPlaying() + + /** + * Called when AOD status is changed + */ + override fun onAlwaysOnChanged(alwaysOn: Boolean) { + alwaysOnEnabled = alwaysOn + } + + override fun addCallback(listener: FoldAodAnimationStatus) { + statusListeners += listener + } + + override fun removeCallback(listener: FoldAodAnimationStatus) { + statusListeners.remove(listener) + } + + interface FoldAodAnimationStatus { + fun onFoldToAodAnimationChanged() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index b53ab210424f..ccde3162b177 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -78,6 +78,8 @@ interface SysUIUnfoldComponent { fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController + fun getFoldAodAnimationController(): FoldAodAnimationController + fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java index a7e9cdbf1a18..8b6e982be55b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java @@ -23,7 +23,6 @@ import com.android.systemui.statusbar.policy.CallbackController; import org.jetbrains.annotations.NotNull; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -38,7 +37,7 @@ import javax.inject.Inject; public class Monitor implements CallbackController<Monitor.Callback> { private final String mTag = getClass().getSimpleName(); - private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); + private final ArrayList<Callback> mCallbacks = new ArrayList<>(); // Set of all conditions that need to be monitored. private final Set<Condition> mConditions; @@ -66,9 +65,9 @@ public class Monitor implements CallbackController<Monitor.Callback> { mAllConditionsMet = newAllConditionsMet; // Updates all callbacks. - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); + final Iterator<Callback> iterator = mCallbacks.iterator(); while (iterator.hasNext()) { - final Callback callback = iterator.next().get(); + final Callback callback = iterator.next(); if (callback == null) { iterator.remove(); } else { @@ -78,7 +77,7 @@ public class Monitor implements CallbackController<Monitor.Callback> { }; @Inject - public Monitor(Set<Condition> conditions) { + public Monitor(Set<Condition> conditions, Set<Callback> callbacks) { mConditions = conditions; // If there is no condition, give green pass. @@ -89,12 +88,20 @@ public class Monitor implements CallbackController<Monitor.Callback> { // Initializes the conditions map and registers a callback for each condition. mConditions.forEach((condition -> mConditionsMap.put(condition, false))); + + if (callbacks == null) { + return; + } + + for (Callback callback : callbacks) { + addCallback(callback); + } } @Override public void addCallback(@NotNull Callback callback) { if (shouldLog()) Log.d(mTag, "adding callback"); - mCallbacks.add(new WeakReference<>(callback)); + mCallbacks.add(callback); // Updates the callback immediately. callback.onConditionsChanged(mAllConditionsMet); @@ -109,9 +116,9 @@ public class Monitor implements CallbackController<Monitor.Callback> { @Override public void removeCallback(@NotNull Callback callback) { if (shouldLog()) Log.d(mTag, "removing callback"); - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); + final Iterator<Callback> iterator = mCallbacks.iterator(); while (iterator.hasNext()) { - final Callback cb = iterator.next().get(); + final Callback cb = iterator.next(); if (cb == null || cb == callback) { iterator.remove(); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java index 1197816e24d2..fc67973fe278 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java @@ -14,29 +14,33 @@ * limitations under the License. */ -package com.android.systemui.communal.conditions; +package com.android.systemui.util.condition.dagger; - -import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS; - -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.util.condition.Condition; import com.android.systemui.util.condition.Monitor; import java.util.Set; -import javax.inject.Inject; -import javax.inject.Named; +import dagger.BindsInstance; +import dagger.Subcomponent; /** - * A concrete implementation of {@Monitor} with conditions for monitoring when communal mode should - * be enabled. + * Component for {@link Monitor}. */ -@SysUISingleton -public class CommunalConditionsMonitor extends Monitor { - @Inject - public CommunalConditionsMonitor( - @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions) { - super(communalConditions); +@Subcomponent +public interface MonitorComponent { + /** + * Factory for {@link MonitorComponent}. + */ + @Subcomponent.Factory + interface Factory { + MonitorComponent create(@BindsInstance Set<Condition> conditions, + @BindsInstance Set<Monitor.Callback> callbacks); } + + /** + * Provides {@link Monitor}. + * @return + */ + Monitor getMonitor(); } diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java index 981bf01164e3..7892d6eec98d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java @@ -18,6 +18,7 @@ package com.android.systemui.util.dagger; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.RingerModeTrackerImpl; +import com.android.systemui.util.condition.dagger.MonitorComponent; import com.android.systemui.util.wrapper.UtilWrapperModule; import dagger.Binds; @@ -26,6 +27,9 @@ import dagger.Module; /** Dagger Module for code in the util package. */ @Module(includes = { UtilWrapperModule.class + }, + subcomponents = { + MonitorComponent.class, }) public interface UtilModule { /** */ diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 6dd6d6d17d58..4600bc71459a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; import com.android.wm.shell.ShellCommandHandler; +import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; @@ -66,7 +67,6 @@ import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.onehanded.OneHandedUiEventLogger; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.protolog.ShellProtoLogImpl; -import com.android.wm.shell.sizecompatui.SizeCompatUI; import com.android.wm.shell.splitscreen.SplitScreen; import java.io.FileDescriptor; @@ -114,7 +114,7 @@ public final class WMShell extends CoreStartable private final Optional<OneHanded> mOneHandedOptional; private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional; private final Optional<ShellCommandHandler> mShellCommandHandler; - private final Optional<SizeCompatUI> mSizeCompatUIOptional; + private final Optional<CompatUI> mCompatUIOptional; private final Optional<DragAndDrop> mDragAndDropOptional; private final CommandQueue mCommandQueue; @@ -132,7 +132,7 @@ public final class WMShell extends CoreStartable private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback; private KeyguardUpdateMonitorCallback mPipKeyguardCallback; private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback; - private KeyguardUpdateMonitorCallback mSizeCompatUIKeyguardCallback; + private KeyguardUpdateMonitorCallback mCompatUIKeyguardCallback; private WakefulnessLifecycle.Observer mWakefulnessObserver; @Inject @@ -143,7 +143,7 @@ public final class WMShell extends CoreStartable Optional<OneHanded> oneHandedOptional, Optional<HideDisplayCutout> hideDisplayCutoutOptional, Optional<ShellCommandHandler> shellCommandHandler, - Optional<SizeCompatUI> sizeCompatUIOptional, + Optional<CompatUI> sizeCompatUIOptional, Optional<DragAndDrop> dragAndDropOptional, CommandQueue commandQueue, ConfigurationController configurationController, @@ -169,7 +169,7 @@ public final class WMShell extends CoreStartable mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; mShellCommandHandler = shellCommandHandler; - mSizeCompatUIOptional = sizeCompatUIOptional; + mCompatUIOptional = sizeCompatUIOptional; mDragAndDropOptional = dragAndDropOptional; mSysUiMainExecutor = sysUiMainExecutor; } @@ -185,7 +185,7 @@ public final class WMShell extends CoreStartable mSplitScreenOptional.ifPresent(this::initSplitScreen); mOneHandedOptional.ifPresent(this::initOneHanded); mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout); - mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi); + mCompatUIOptional.ifPresent(this::initCompatUi); mDragAndDropOptional.ifPresent(this::initDragAndDrop); } @@ -391,14 +391,14 @@ public final class WMShell extends CoreStartable } @VisibleForTesting - void initSizeCompatUi(SizeCompatUI sizeCompatUI) { - mSizeCompatUIKeyguardCallback = new KeyguardUpdateMonitorCallback() { + void initCompatUi(CompatUI sizeCompatUI) { + mCompatUIKeyguardCallback = new KeyguardUpdateMonitorCallback() { @Override public void onKeyguardOccludedChanged(boolean occluded) { sizeCompatUI.onKeyguardOccludedChanged(occluded); } }; - mKeyguardUpdateMonitor.registerCallback(mSizeCompatUIKeyguardCallback); + mKeyguardUpdateMonitor.registerCallback(mCompatUIKeyguardCallback); } void initDragAndDrop(DragAndDrop dragAndDrop) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index e967033b69a2..74e0f4002026 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -264,7 +264,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { reset(mView); observer.onChange(true); mExecutor.runAllReady(); - verify(mView).switchToClock(KeyguardClockSwitch.SMALL); + verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true); } private void verifyAttachment(VerificationMode times) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index e4336fe07dbb..8717a0eaf57f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -253,8 +253,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { } @Test - public void switchingToBigClock_makesSmallClockDisappear() { - mKeyguardClockSwitch.switchToClock(LARGE); + public void switchingToBigClockWithAnimation_makesSmallClockDisappear() { + mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true); mKeyguardClockSwitch.mClockInAnim.end(); mKeyguardClockSwitch.mClockOutAnim.end(); @@ -265,8 +265,17 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { } @Test - public void switchingToSmallClock_makesBigClockDisappear() { - mKeyguardClockSwitch.switchToClock(SMALL); + public void switchingToBigClockNoAnimation_makesSmallClockDisappear() { + mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ false); + + assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); + assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); + assertThat(mClockFrame.getAlpha()).isEqualTo(0); + } + + @Test + public void switchingToSmallClockWithAnimation_makesBigClockDisappear() { + mKeyguardClockSwitch.switchToClock(SMALL, /* animate */ true); mKeyguardClockSwitch.mClockInAnim.end(); mKeyguardClockSwitch.mClockOutAnim.end(); @@ -279,8 +288,19 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { } @Test + public void switchingToSmallClockNoAnimation_makesBigClockDisappear() { + mKeyguardClockSwitch.switchToClock(SMALL, false); + + assertThat(mClockFrame.getAlpha()).isEqualTo(1); + assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE); + // only big clock is removed at switch + assertThat(mLargeClockFrame.getParent()).isNull(); + assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); + } + + @Test public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() { - assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isTrue(); - assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isFalse(); + assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue(); + assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 77762859de18..24b01e079b42 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -244,7 +244,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( R.id.keyguard_bouncer_user_switcher); assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity) - .isEqualTo(Gravity.LEFT | Gravity.TOP); + .isEqualTo(Gravity.LEFT | Gravity.CENTER_VERTICAL); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java index 9a9b7c4f62eb..4a29ada8a998 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java @@ -28,8 +28,8 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.communal.conditions.CommunalConditionsMonitor; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.condition.Monitor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -45,7 +45,7 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase { @Mock private CommunalManager mCommunalManager; @Mock - private CommunalConditionsMonitor mCommunalConditionsMonitor; + private Monitor mCommunalConditionsMonitor; private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @@ -55,7 +55,7 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase { mContext.addMockSystemService(CommunalManager.class, mCommunalManager); doAnswer(invocation -> { - final CommunalConditionsMonitor.Callback callback = invocation.getArgument(0); + final Monitor.Callback callback = invocation.getArgument(0); callback.onConditionsChanged(true); return null; }).when(mCommunalConditionsMonitor).addCallback(any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java index 409dd940ceb6..df1cc766d162 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java @@ -31,8 +31,8 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.communal.conditions.CommunalConditionsMonitor; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.condition.Monitor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -49,9 +49,9 @@ import java.lang.ref.WeakReference; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class CommunalSourceMonitorTest extends SysuiTestCase { - @Mock private CommunalConditionsMonitor mCommunalConditionsMonitor; + @Mock private Monitor mCommunalConditionsMonitor; - @Captor private ArgumentCaptor<CommunalConditionsMonitor.Callback> mConditionsCallbackCaptor; + @Captor private ArgumentCaptor<Monitor.Callback> mConditionsCallbackCaptor; private CommunalSourceMonitor mCommunalSourceMonitor; private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @@ -156,7 +156,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase { private void setConditionsMet(boolean value) { mExecutor.runAllReady(); verify(mCommunalConditionsMonitor).addCallback(mConditionsCallbackCaptor.capture()); - final CommunalConditionsMonitor.Callback conditionsCallback = + final Monitor.Callback conditionsCallback = mConditionsCallbackCaptor.getValue(); conditionsCallback.onConditionsChanged(value); mExecutor.runAllReady(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java index 873f7a45571b..55af51d3fddf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java @@ -45,6 +45,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.unfold.FoldAodAnimationController; +import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.wakelock.WakeLockFake; import org.junit.After; @@ -54,6 +56,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + @RunWith(AndroidJUnit4.class) @SmallTest public class DozeUiTest extends SysuiTestCase { @@ -79,6 +83,10 @@ public class DozeUiTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock + private FoldAodAnimationController mFoldAodAnimationController; + @Mock + private SysUIUnfoldComponent mSysUIUnfoldComponent; + @Mock private ConfigurationController mConfigurationController; @Before @@ -90,9 +98,13 @@ public class DozeUiTest extends SysuiTestCase { mWakeLock = new WakeLockFake(); mHandler = mHandlerThread.getThreadHandler(); + when(mSysUIUnfoldComponent.getFoldAodAnimationController()) + .thenReturn(mFoldAodAnimationController); + mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - mStatusBarStateController, mConfigurationController); + mStatusBarStateController, Optional.of(mSysUIUnfoldComponent), + mConfigurationController); mDozeUi.setDozeMachine(mMachine); } @@ -121,6 +133,7 @@ public class DozeUiTest extends SysuiTestCase { reset(mHost); when(mDozeParameters.getAlwaysOn()).thenReturn(false); when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false); + when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true); mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false); verify(mHost).setAnimateScreenOff(eq(false)); @@ -131,6 +144,7 @@ public class DozeUiTest extends SysuiTestCase { reset(mHost); when(mDozeParameters.getAlwaysOn()).thenReturn(true); when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false); + when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true); // Take over when the keyguard is visible. mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true); @@ -142,6 +156,18 @@ public class DozeUiTest extends SysuiTestCase { } @Test + public void propagatesAnimateScreenOff_alwaysOn_shouldAnimateDozingChangeIsFalse() { + reset(mHost); + when(mDozeParameters.getAlwaysOn()).thenReturn(true); + when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false); + when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(false); + + // Take over when the keyguard is visible. + mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true); + verify(mHost).setAnimateScreenOff(eq(false)); + } + + @Test public void neverAnimateScreenOff_whenNotSupported() { // Re-initialize DozeParameters saying that the display requires blanking. reset(mDozeParameters); @@ -149,7 +175,8 @@ public class DozeUiTest extends SysuiTestCase { when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true); mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - mStatusBarStateController, mConfigurationController); + mStatusBarStateController, Optional.of(mSysUIUnfoldComponent), + mConfigurationController); mDozeUi.setDozeMachine(mMachine); // Never animate if display doesn't support it. diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 53bfeee9135a..d0ec3f36427c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; +import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -78,16 +79,41 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Mock DreamOverlayStateController mDreamOverlayStateController; + @Mock + DreamOverlayComponent.Factory mDreamOverlayStatusBarViewComponentFactory; + + @Mock + DreamOverlayComponent mDreamOverlayComponent; + + @Mock + DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController; + + @Mock + DreamOverlayContainerView mDreamOverlayContainerView; + + @Mock + ViewGroup mDreamOverlayContentView; + @Before public void setup() { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(WindowManager.class, mWindowManager); + + when(mDreamOverlayComponent.getDreamOverlayContentView()) + .thenReturn(mDreamOverlayContentView); + when(mDreamOverlayComponent.getDreamOverlayContainerView()) + .thenReturn(mDreamOverlayContainerView); + when(mDreamOverlayComponent.getDreamOverlayStatusBarViewController()) + .thenReturn(mDreamOverlayStatusBarViewController); + when(mDreamOverlayStatusBarViewComponentFactory.create()) + .thenReturn(mDreamOverlayComponent); + } @Test public void testInteraction() throws Exception { final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor, - mDreamOverlayStateController); + mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory); final IBinder proxy = service.onBind(new Intent()); final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); clearInvocations(mWindowManager); @@ -128,7 +154,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testListening() throws Exception { final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor, - mDreamOverlayStateController); + mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory); final IBinder proxy = service.onBind(new Intent()); final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); @@ -150,4 +176,19 @@ public class DreamOverlayServiceTest extends SysuiTestCase { // Verify provider is asked to create overlay. verify(mProvider).onCreateOverlay(any(), any(), any()); } + + @Test + public void testDreamOverlayStatusBarViewControllerInitialized() throws Exception { + final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor, + mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory); + + final IBinder proxy = service.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback); + mMainExecutor.runAllReady(); + + verify(mDreamOverlayStatusBarViewController).init(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java new file mode 100644 index 000000000000..7f72dda441d2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.battery.BatteryMeterViewController; +import com.android.systemui.statusbar.policy.BatteryController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { + + @Mock + DreamOverlayStatusBarView mView; + @Mock + BatteryController mBatteryController; + @Mock + BatteryMeterViewController mBatteryMeterViewController; + @Mock + ConnectivityManager mConnectivityManager; + @Mock + NetworkCapabilities mNetworkCapabilities; + @Mock + Network mNetwork; + + DreamOverlayStatusBarViewController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mController = new DreamOverlayStatusBarViewController( + mContext, mView, mBatteryController, mBatteryMeterViewController, + mConnectivityManager); + } + + @Test + public void testOnInitInitializesControllers() { + mController.onInit(); + verify(mBatteryMeterViewController).init(); + } + + @Test + public void testOnViewAttachedAddsBatteryControllerCallback() { + mController.onViewAttached(); + verify(mBatteryController) + .addCallback(any(BatteryController.BatteryStateChangeCallback.class)); + } + + @Test + public void testOnViewAttachedRegistersNetworkCallback() { + mController.onViewAttached(); + verify(mConnectivityManager) + .registerNetworkCallback(any(NetworkRequest.class), any( + ConnectivityManager.NetworkCallback.class)); + } + + @Test + public void testOnViewAttachedShowsWifiStatusWhenWifiUnavailable() { + when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) + .thenReturn(false); + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); + mController.onViewAttached(); + verify(mView).showWifiStatus(true); + } + + @Test + public void testOnViewAttachedHidesWifiStatusWhenWifiAvailable() { + when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) + .thenReturn(true); + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); + mController.onViewAttached(); + verify(mView).showWifiStatus(false); + } + + @Test + public void testOnViewAttachedShowsWifiStatusWhenNetworkCapabilitiesUnavailable() { + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(null); + mController.onViewAttached(); + verify(mView).showWifiStatus(true); + } + + @Test + public void testOnViewDetachedRemovesBatteryControllerCallback() { + mController.onViewDetached(); + verify(mBatteryController) + .removeCallback(any(BatteryController.BatteryStateChangeCallback.class)); + } + + @Test + public void testOnViewDetachedUnregistersNetworkCallback() { + mController.onViewDetached(); + verify(mConnectivityManager) + .unregisterNetworkCallback(any(ConnectivityManager.NetworkCallback.class)); + } + + @Test + public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() { + final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture = + ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class); + mController.onViewAttached(); + verify(mBatteryController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onBatteryLevelChanged(1, true, true); + verify(mView).showBatteryPercentText(true); + } + + @Test + public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() { + final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture = + ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class); + mController.onViewAttached(); + verify(mBatteryController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onBatteryLevelChanged(1, true, false); + verify(mView).showBatteryPercentText(false); + } + + @Test + public void testWifiStatusHiddenWhenWifiBecomesAvailable() { + // Make sure wifi starts out unavailable when onViewAttached is called. + when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) + .thenReturn(false); + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); + mController.onViewAttached(); + + final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture()); + callbackCapture.getValue().onAvailable(mNetwork); + verify(mView).showWifiStatus(false); + } + + @Test + public void testWifiStatusShownWhenWifiBecomesUnavailable() { + // Make sure wifi starts out available when onViewAttached is called. + when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) + .thenReturn(true); + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); + mController.onViewAttached(); + + final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture()); + callbackCapture.getValue().onLost(mNetwork); + verify(mView).showWifiStatus(true); + } + + @Test + public void testWifiStatusHiddenWhenCapabilitiesChange() { + // Make sure wifi starts out unavailable when onViewAttached is called. + when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) + .thenReturn(false); + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); + mController.onViewAttached(); + + final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture()); + when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) + .thenReturn(true); + callbackCapture.getValue().onCapabilitiesChanged(mNetwork, mNetworkCapabilities); + verify(mView).showWifiStatus(false); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 8d329c56af36..27fcb11b8dfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.util.DeviceConfigProxy; @@ -69,7 +70,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -95,62 +95,40 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock NavigationModeController mNavigationModeController; private @Mock KeyguardDisplayManager mKeyguardDisplayManager; private @Mock DozeParameters mDozeParameters; - private @Mock Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent; - private @Mock Optional<UnfoldLightRevealOverlayAnimation> mUnfoldAnimationOptional; + private @Mock SysUIUnfoldComponent mSysUIUnfoldComponent; private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation; private @Mock SysuiStatusBarStateController mStatusBarStateController; private @Mock KeyguardStateController mKeyguardStateController; private @Mock NotificationShadeDepthController mNotificationShadeDepthController; private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock ScreenOffAnimationController mScreenOffAnimationController; + private @Mock FoldAodAnimationController mFoldAodAnimationController; private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback; private @Mock InteractionJankMonitor mInteractionJankMonitor; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); + private Optional<SysUIUnfoldComponent> mSysUiUnfoldComponentOptional; + private FalsingCollectorFake mFalsingCollector; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mFalsingCollector = new FalsingCollectorFake(); + mSysUiUnfoldComponentOptional = Optional.of(mSysUIUnfoldComponent); when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager); when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class)); - when(mSysUIUnfoldComponent.map( - ArgumentMatchers.<Function<SysUIUnfoldComponent, UnfoldLightRevealOverlayAnimation>> - any())) - .thenReturn(mUnfoldAnimationOptional); - when(mUnfoldAnimationOptional.isPresent()).thenReturn(true); - when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation); + when(mSysUIUnfoldComponent.getUnfoldLightRevealOverlayAnimation()) + .thenReturn(mUnfoldAnimation); + when(mSysUIUnfoldComponent.getFoldAodAnimationController()) + .thenReturn(mFoldAodAnimationController); + when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true); when(mInteractionJankMonitor.end(anyInt())).thenReturn(true); - mViewMediator = new KeyguardViewMediator( - mContext, - mFalsingCollector, - mLockPatternUtils, - mBroadcastDispatcher, - () -> mStatusBarKeyguardViewManager, - mDismissCallbackRegistry, - mUpdateMonitor, - mDumpManager, - mUiBgExecutor, - mPowerManager, - mTrustManager, - mUserSwitcherController, - mDeviceConfig, - mNavigationModeController, - mKeyguardDisplayManager, - mDozeParameters, - mSysUIUnfoldComponent, - mStatusBarStateController, - mKeyguardStateController, - () -> mKeyguardUnlockAnimationController, - mScreenOffAnimationController, - () -> mNotificationShadeDepthController, - mInteractionJankMonitor); - mViewMediator.start(); + createAndStartViewMediator(); } @Test @@ -179,6 +157,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback); TestableLooper.get(this).processAllMessages(); onUnfoldOverlayReady(); + onFoldAodReady(); // Should be called when both unfold overlay and keyguard drawn ready verify(mKeyguardDrawnCallback).onDrawn(); @@ -188,7 +167,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @TestableLooper.RunWithLooper(setAsMainLooper = true) public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() throws RemoteException { - when(mUnfoldAnimationOptional.isPresent()).thenReturn(false); + mSysUiUnfoldComponentOptional = Optional.empty(); + createAndStartViewMediator(); mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback); TestableLooper.get(this).processAllMessages(); @@ -200,6 +180,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test public void testIsAnimatingScreenOff() { when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true); mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false); mViewMediator.setDozing(true); @@ -244,4 +225,39 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { overlayReadyCaptor.getValue().run(); TestableLooper.get(this).processAllMessages(); } + + private void onFoldAodReady() { + ArgumentCaptor<Runnable> ready = ArgumentCaptor.forClass(Runnable.class); + verify(mFoldAodAnimationController).onScreenTurningOn(ready.capture()); + ready.getValue().run(); + TestableLooper.get(this).processAllMessages(); + } + + private void createAndStartViewMediator() { + mViewMediator = new KeyguardViewMediator( + mContext, + mFalsingCollector, + mLockPatternUtils, + mBroadcastDispatcher, + () -> mStatusBarKeyguardViewManager, + mDismissCallbackRegistry, + mUpdateMonitor, + mDumpManager, + mUiBgExecutor, + mPowerManager, + mTrustManager, + mUserSwitcherController, + mDeviceConfig, + mNavigationModeController, + mKeyguardDisplayManager, + mDozeParameters, + mSysUiUnfoldComponentOptional, + mStatusBarStateController, + mKeyguardStateController, + () -> mKeyguardUnlockAnimationController, + mScreenOffAnimationController, + () -> mNotificationShadeDepthController, + mInteractionJankMonitor); + mViewMediator.start(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt new file mode 100644 index 000000000000..af33dafb6107 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tileimpl + +import android.content.ComponentName +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.external.CustomTile +import com.android.systemui.qs.tiles.AirplaneModeTile +import com.android.systemui.qs.tiles.AlarmTile +import com.android.systemui.qs.tiles.BatterySaverTile +import com.android.systemui.qs.tiles.BluetoothTile +import com.android.systemui.qs.tiles.CameraToggleTile +import com.android.systemui.qs.tiles.CastTile +import com.android.systemui.qs.tiles.CellularTile +import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.DataSaverTile +import com.android.systemui.qs.tiles.DeviceControlsTile +import com.android.systemui.qs.tiles.DndTile +import com.android.systemui.qs.tiles.FgsManagerTile +import com.android.systemui.qs.tiles.FlashlightTile +import com.android.systemui.qs.tiles.HotspotTile +import com.android.systemui.qs.tiles.InternetTile +import com.android.systemui.qs.tiles.LocationTile +import com.android.systemui.qs.tiles.MicrophoneToggleTile +import com.android.systemui.qs.tiles.NfcTile +import com.android.systemui.qs.tiles.NightDisplayTile +import com.android.systemui.qs.tiles.OneHandedModeTile +import com.android.systemui.qs.tiles.QRCodeScannerTile +import com.android.systemui.qs.tiles.QuickAccessWalletTile +import com.android.systemui.qs.tiles.ReduceBrightColorsTile +import com.android.systemui.qs.tiles.RotationLockTile +import com.android.systemui.qs.tiles.ScreenRecordTile +import com.android.systemui.qs.tiles.UiModeNightTile +import com.android.systemui.qs.tiles.UserTile +import com.android.systemui.qs.tiles.WifiTile +import com.android.systemui.qs.tiles.WorkModeTile +import com.android.systemui.util.leak.GarbageMonitor +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.Mockito.inOrder +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +private val specMap = mapOf( + "wifi" to WifiTile::class.java, + "internet" to InternetTile::class.java, + "bt" to BluetoothTile::class.java, + "cell" to CellularTile::class.java, + "dnd" to DndTile::class.java, + "inversion" to ColorInversionTile::class.java, + "airplane" to AirplaneModeTile::class.java, + "work" to WorkModeTile::class.java, + "rotation" to RotationLockTile::class.java, + "flashlight" to FlashlightTile::class.java, + "location" to LocationTile::class.java, + "cast" to CastTile::class.java, + "hotspot" to HotspotTile::class.java, + "user" to UserTile::class.java, + "battery" to BatterySaverTile::class.java, + "saver" to DataSaverTile::class.java, + "night" to NightDisplayTile::class.java, + "nfc" to NfcTile::class.java, + "dark" to UiModeNightTile::class.java, + "screenrecord" to ScreenRecordTile::class.java, + "reduce_brightness" to ReduceBrightColorsTile::class.java, + "cameratoggle" to CameraToggleTile::class.java, + "mictoggle" to MicrophoneToggleTile::class.java, + "controls" to DeviceControlsTile::class.java, + "alarm" to AlarmTile::class.java, + "wallet" to QuickAccessWalletTile::class.java, + "qr_code_scanner" to QRCodeScannerTile::class.java, + "onehanded" to OneHandedModeTile::class.java, + "fgsmanager" to FgsManagerTile::class.java +) + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class QSFactoryImplTest : SysuiTestCase() { + + @Mock private lateinit var qsHost: QSHost + @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder + @Mock private lateinit var customTile: CustomTile + + @Mock private lateinit var wifiTile: WifiTile + @Mock private lateinit var internetTile: InternetTile + @Mock private lateinit var bluetoothTile: BluetoothTile + @Mock private lateinit var cellularTile: CellularTile + @Mock private lateinit var dndTile: DndTile + @Mock private lateinit var colorInversionTile: ColorInversionTile + @Mock private lateinit var airplaneTile: AirplaneModeTile + @Mock private lateinit var workTile: WorkModeTile + @Mock private lateinit var rotationTile: RotationLockTile + @Mock private lateinit var flashlightTile: FlashlightTile + @Mock private lateinit var locationTile: LocationTile + @Mock private lateinit var castTile: CastTile + @Mock private lateinit var hotspotTile: HotspotTile + @Mock private lateinit var userTile: UserTile + @Mock private lateinit var batterySaverTile: BatterySaverTile + @Mock private lateinit var dataSaverTile: DataSaverTile + @Mock private lateinit var nightDisplayTile: NightDisplayTile + @Mock private lateinit var nfcTile: NfcTile + @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile + @Mock private lateinit var darkModeTile: UiModeNightTile + @Mock private lateinit var screenRecordTile: ScreenRecordTile + @Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile + @Mock private lateinit var cameraToggleTile: CameraToggleTile + @Mock private lateinit var microphoneToggleTile: MicrophoneToggleTile + @Mock private lateinit var deviceControlsTile: DeviceControlsTile + @Mock private lateinit var alarmTile: AlarmTile + @Mock private lateinit var quickAccessWalletTile: QuickAccessWalletTile + @Mock private lateinit var qrCodeScannerTile: QRCodeScannerTile + @Mock private lateinit var oneHandedModeTile: OneHandedModeTile + @Mock private lateinit var fgsManagerTile: FgsManagerTile + + private lateinit var factory: QSFactoryImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(qsHost.context).thenReturn(mContext) + whenever(qsHost.userContext).thenReturn(mContext) + whenever(customTileBuilder.build()).thenReturn(customTile) + + factory = QSFactoryImpl( + { qsHost }, + { customTileBuilder }, + { wifiTile }, + { internetTile }, + { bluetoothTile }, + { cellularTile }, + { dndTile }, + { colorInversionTile }, + { airplaneTile }, + { workTile }, + { rotationTile }, + { flashlightTile }, + { locationTile }, + { castTile }, + { hotspotTile }, + { userTile }, + { batterySaverTile }, + { dataSaverTile }, + { nightDisplayTile }, + { nfcTile }, + { memoryTile }, + { darkModeTile }, + { screenRecordTile }, + { reduceBrightColorsTile }, + { cameraToggleTile }, + { microphoneToggleTile }, + { deviceControlsTile }, + { alarmTile }, + { quickAccessWalletTile }, + { qrCodeScannerTile }, + { oneHandedModeTile }, + { fgsManagerTile } + ) + // When adding/removing tiles, fix also [specMap] + } + + @Test + fun testCorrectTileClassStock() { + specMap.forEach { spec, klazz -> + assertThat(factory.createTile(spec)).isInstanceOf(klazz) + } + } + + @Test + fun testCustomTileClass() { + val customSpec = CustomTile.toSpec(ComponentName("test", "test")) + assertThat(factory.createTile(customSpec)).isInstanceOf(CustomTile::class.java) + } + + @Test + fun testBadTileNull() { + assertThat(factory.createTile("-432~")).isNull() + } + + @Test + fun testTileInitializedAndStale() { + specMap.forEach { spec, _ -> + val tile = factory.createTile(spec) as QSTileImpl<*> + val inOrder = inOrder(tile) + inOrder.verify(tile).initialize() + inOrder.verify(tile).postStale() + } + + val customSpec = CustomTile.toSpec(ComponentName("test", "test")) + val tile = factory.createTile(customSpec) as QSTileImpl<*> + val inOrder = inOrder(tile) + inOrder.verify(tile).initialize() + inOrder.verify(tile).postStale() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 9bcdcc96767a..1cd9b9e77423 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -425,6 +425,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .thenReturn(mKeyguardUserSwitcherComponent); when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController()) .thenReturn(mKeyguardUserSwitcherController); + when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true); doAnswer((Answer<Void>) invocation -> { mTouchHandler = invocation.getArgument(0); @@ -880,11 +881,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); triggerPositionClockAndNotifications(); - verify(mKeyguardStatusViewController).displayClock(LARGE); + verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); mNotificationPanelViewController.closeQs(); - verify(mKeyguardStatusViewController).displayClock(SMALL); + verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true); } @Test @@ -894,12 +895,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); triggerPositionClockAndNotifications(); - verify(mKeyguardStatusViewController).displayClock(LARGE); + verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); triggerPositionClockAndNotifications(); - verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE); - verify(mKeyguardStatusViewController, never()).displayClock(SMALL); + verify(mKeyguardStatusViewController, times(2)) + .displayClock(LARGE, /* animate */ true); + verify(mKeyguardStatusViewController, never()) + .displayClock(SMALL, /* animate */ true); } @Test @@ -911,7 +914,20 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mNotificationPanelViewController.setDozing(true, false, null); - verify(mKeyguardStatusViewController).displayClock(LARGE); + verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); + } + + @Test + public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() { + when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); + mStatusBarStateController.setState(KEYGUARD); + enableSplitShade(/* enabled= */ true); + when(mMediaDataManager.hasActiveMedia()).thenReturn(true); + when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); + + mNotificationPanelViewController.setDozing(true, false, null); + + verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false); } @Test @@ -923,13 +939,13 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { // one notification + media player visible when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); triggerPositionClockAndNotifications(); - verify(mKeyguardStatusViewController).displayClock(SMALL); + verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true); // only media player visible when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); triggerPositionClockAndNotifications(); - verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL); - verify(mKeyguardStatusViewController, never()).displayClock(LARGE); + verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true); + verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt index 2d548e96e598..a8544a9a15e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.phone -import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation @@ -42,6 +42,7 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() { var viewVisibility = View.GONE private lateinit var splitShadeHeaderController: SplitShadeHeaderController + private lateinit var carrierIconSlots: List<String> @Before fun setup() { @@ -67,12 +68,13 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() { featureFlags, batteryMeterViewController ) + carrierIconSlots = listOf( + context.getString(com.android.internal.R.string.status_bar_mobile)) } @Test fun setVisible_onlyInSplitShade() { - splitShadeHeaderController.splitShadeMode = true - splitShadeHeaderController.shadeExpanded = true + makeShadeVisible() assertThat(viewVisibility).isEqualTo(View.VISIBLE) splitShadeHeaderController.splitShadeMode = false @@ -81,17 +83,38 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() { @Test fun updateListeners_registersWhenVisible() { - splitShadeHeaderController.splitShadeMode = true - splitShadeHeaderController.shadeExpanded = true + makeShadeVisible() verify(qsCarrierGroupController).setListening(true) verify(statusBarIconController).addIconGroup(any()) } @Test fun shadeExpandedFraction_updatesAlpha() { - splitShadeHeaderController.splitShadeMode = true - splitShadeHeaderController.shadeExpanded = true + makeShadeVisible() splitShadeHeaderController.shadeExpandedFraction = 0.5f verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f)) } -}
\ No newline at end of file + + @Test + fun singleCarrier_enablesCarrierIconsInStatusIcons() { + whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true) + + makeShadeVisible() + + verify(statusIcons).removeIgnoredSlots(carrierIconSlots) + } + + @Test + fun dualCarrier_disablesCarrierIconsInStatusIcons() { + whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false) + + makeShadeVisible() + + verify(statusIcons).addIgnoredSlots(carrierIconSlots) + } + + private fun makeShadeVisible() { + splitShadeHeaderController.splitShadeMode = true + splitShadeHeaderController.shadeExpanded = true + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index c5bdfed6082b..cbaa460d711b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -43,9 +43,6 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.DismissCallbackRegistry; -import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.statusbar.NotificationMediaManager; @@ -79,9 +76,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private NotificationPanelViewController mNotificationPanelView; @Mock - private BiometricUnlockController mBiometrucUnlockController; - @Mock - private DismissCallbackRegistry mDismissCallbackRegistry; + private BiometricUnlockController mBiometricUnlockController; @Mock private SysuiStatusBarStateController mStatusBarStateController; @Mock @@ -97,15 +92,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private KeyguardBouncer mBouncer; @Mock - private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; - @Mock private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor; @Mock private KeyguardMessageArea mKeyguardMessageArea; @Mock private ShadeController mShadeController; - private WakefulnessLifecycle mWakefulnessLifecycle; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Before @@ -117,10 +109,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(mBouncer); when(mStatusBar.getBouncerContainer()).thenReturn(mContainer); when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea); - mWakefulnessLifecycle = new WakefulnessLifecycle( - getContext(), - null, - mock(DumpManager.class)); mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager( getContext(), mViewMediatorCallback, @@ -134,15 +122,13 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mKeyguardStateController, mock(NotificationMediaManager.class), mKeyguardBouncerFactory, - mWakefulnessLifecycle, - mUnlockedScreenOffAnimationController, mKeyguardMessageAreaFactory, () -> mShadeController); mStatusBarKeyguardViewManager.registerStatusBar( mStatusBar, mNotificationPanelView, new PanelExpansionStateManager(), - mBiometrucUnlockController, + mBiometricUnlockController, mNotificationContainer, mBypassController); mStatusBarKeyguardViewManager.show(null); @@ -261,7 +247,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() { - when(mBiometrucUnlockController.getMode()) + when(mBiometricUnlockController.getMode()) .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); mStatusBarKeyguardViewManager.onPanelExpansionChanged( /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java index 878bdeac43c9..d6454490e62a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java @@ -58,7 +58,7 @@ public class ConditionMonitorTest extends SysuiTestCase { mCondition3 = spy(new FakeCondition()); mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3)); - mConditionMonitor = new Monitor(mConditions); + mConditionMonitor = new Monitor(mConditions, null /*callbacks*/); } @Test @@ -98,7 +98,7 @@ public class ConditionMonitorTest extends SysuiTestCase { @Test public void addCallback_noConditions_reportAllConditionsMet() { - final Monitor monitor = new Monitor(new HashSet<>()); + final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/); final Monitor.Callback callback = mock(Monitor.Callback.class); monitor.addCallback(callback); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 1e15d2ae0bdb..2f2e536322db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; @@ -42,7 +43,6 @@ import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.sizecompatui.SizeCompatUI; import com.android.wm.shell.splitscreen.SplitScreen; import org.junit.Before; @@ -78,7 +78,7 @@ public class WMShellTest extends SysuiTestCase { @Mock WakefulnessLifecycle mWakefulnessLifecycle; @Mock ProtoTracer mProtoTracer; @Mock ShellCommandHandler mShellCommandHandler; - @Mock SizeCompatUI mSizeCompatUI; + @Mock CompatUI mCompatUI; @Mock ShellExecutor mSysUiMainExecutor; @Mock DragAndDrop mDragAndDrop; @@ -88,7 +88,7 @@ public class WMShellTest extends SysuiTestCase { mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen), Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), - Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI), + Optional.of(mShellCommandHandler), Optional.of(mCompatUI), Optional.of(mDragAndDrop), mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor, mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer, @@ -136,8 +136,8 @@ public class WMShellTest extends SysuiTestCase { } @Test - public void initSizeCompatUI_registersCallbacks() { - mWMShell.initSizeCompatUi(mSizeCompatUI); + public void initCompatUI_registersCallbacks() { + mWMShell.initCompatUi(mCompatUI); verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class)); } diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml index 1f5834d72dd0..a03dcbd3941d 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml @@ -18,5 +18,5 @@ --> <resources> <!-- The height of the bottom navigation gesture area. --> - <dimen name="navigation_bar_gesture_height">32dp</dimen> + <dimen name="navigation_bar_gesture_height">24dp</dimen> </resources> diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml index ac1f0226be52..c5d0c9e3f348 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml @@ -18,13 +18,13 @@ --> <resources> <!-- Height of the bottom navigation / system bar. --> - <dimen name="navigation_bar_height">16dp</dimen> + <dimen name="navigation_bar_height">24dp</dimen> <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --> - <dimen name="navigation_bar_height_landscape">16dp</dimen> + <dimen name="navigation_bar_height_landscape">24dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> - <dimen name="navigation_bar_width">16dp</dimen> + <dimen name="navigation_bar_width">24dp</dimen> <!-- Height of the bottom navigation / system bar. --> <dimen name="navigation_bar_frame_height">48dp</dimen> <!-- The height of the bottom navigation gesture area. --> - <dimen name="navigation_bar_gesture_height">32dp</dimen> + <dimen name="navigation_bar_gesture_height">24dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml index ac1f0226be52..c5d0c9e3f348 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml @@ -18,13 +18,13 @@ --> <resources> <!-- Height of the bottom navigation / system bar. --> - <dimen name="navigation_bar_height">16dp</dimen> + <dimen name="navigation_bar_height">24dp</dimen> <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --> - <dimen name="navigation_bar_height_landscape">16dp</dimen> + <dimen name="navigation_bar_height_landscape">24dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> - <dimen name="navigation_bar_width">16dp</dimen> + <dimen name="navigation_bar_width">24dp</dimen> <!-- Height of the bottom navigation / system bar. --> <dimen name="navigation_bar_frame_height">48dp</dimen> <!-- The height of the bottom navigation gesture area. --> - <dimen name="navigation_bar_gesture_height">32dp</dimen> + <dimen name="navigation_bar_gesture_height">24dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml index ac1f0226be52..c5d0c9e3f348 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml @@ -18,13 +18,13 @@ --> <resources> <!-- Height of the bottom navigation / system bar. --> - <dimen name="navigation_bar_height">16dp</dimen> + <dimen name="navigation_bar_height">24dp</dimen> <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --> - <dimen name="navigation_bar_height_landscape">16dp</dimen> + <dimen name="navigation_bar_height_landscape">24dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> - <dimen name="navigation_bar_width">16dp</dimen> + <dimen name="navigation_bar_width">24dp</dimen> <!-- Height of the bottom navigation / system bar. --> <dimen name="navigation_bar_frame_height">48dp</dimen> <!-- The height of the bottom navigation gesture area. --> - <dimen name="navigation_bar_gesture_height">32dp</dimen> + <dimen name="navigation_bar_gesture_height">24dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml index ac1f0226be52..c5d0c9e3f348 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml @@ -18,13 +18,13 @@ --> <resources> <!-- Height of the bottom navigation / system bar. --> - <dimen name="navigation_bar_height">16dp</dimen> + <dimen name="navigation_bar_height">24dp</dimen> <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --> - <dimen name="navigation_bar_height_landscape">16dp</dimen> + <dimen name="navigation_bar_height_landscape">24dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> - <dimen name="navigation_bar_width">16dp</dimen> + <dimen name="navigation_bar_width">24dp</dimen> <!-- Height of the bottom navigation / system bar. --> <dimen name="navigation_bar_frame_height">48dp</dimen> <!-- The height of the bottom navigation gesture area. --> - <dimen name="navigation_bar_gesture_height">32dp</dimen> + <dimen name="navigation_bar_gesture_height">24dp</dimen> </resources>
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 881c910b399b..f050b6622a5d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1027,8 +1027,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mSystemSupport.getMagnificationProcessor(); final long identity = Binder.clearCallingIdentity(); try { - magnificationProcessor.getMagnificationRegion(displayId, region, - mSecurityPolicy.canControlMagnification(this)); + magnificationProcessor.getFullscreenMagnificationRegion(displayId, + region, mSecurityPolicy.canControlMagnification(this)); return region; } finally { Binder.restoreCallingIdentity(identity); @@ -1095,7 +1095,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { MagnificationProcessor magnificationProcessor = mSystemSupport.getMagnificationProcessor(); - return (magnificationProcessor.reset(displayId, animate) + return (magnificationProcessor.resetFullscreenMagnification(displayId, animate) || !magnificationProcessor.isMagnifying(displayId)); } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java index d0b989582ebe..7a525ee23d27 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java @@ -119,6 +119,18 @@ public class MagnificationProcessor { return false; } + private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale, + float centerX, float centerY, + boolean animate, int id) { + if (!isRegistered(displayId)) { + register(displayId); + } + return mController.getFullScreenMagnificationController().setScaleAndCenter( + displayId, + scale, + centerX, centerY, animate, id); + } + /** * Returns {@code true} if transition magnification mode needed. And it is no need to transition * mode when the controlling mode is unchanged or the controlling magnifier is not activated. @@ -135,24 +147,18 @@ public class MagnificationProcessor { } /** - * Returns the magnification scale. If an animation is in progress, - * this reflects the end state of the animation. + * Returns the magnification scale of full-screen magnification on the display. + * If an animation is in progress, this reflects the end state of the animation. * * @param displayId The logical display id. * @return the scale */ public float getScale(int displayId) { - int mode = getControllingMode(displayId); - if (mode == MAGNIFICATION_MODE_FULLSCREEN) { - return mController.getFullScreenMagnificationController().getScale(displayId); - } else if (mode == MAGNIFICATION_MODE_WINDOW) { - return mController.getWindowMagnificationMgr().getScale(displayId); - } - return 0; + return mController.getFullScreenMagnificationController().getScale(displayId); } /** - * Returns the magnification center in X coordinate of the controlling magnification mode. + * Returns the magnification center in X coordinate of full-screen magnification. * If the service can control magnification but fullscreen magnifier is not registered, it will * register the magnifier for this call then unregister the magnifier finally to make the * magnification center correct. @@ -162,25 +168,19 @@ public class MagnificationProcessor { * @return the X coordinate */ public float getCenterX(int displayId, boolean canControlMagnification) { - int mode = getControllingMode(displayId); - if (mode == MAGNIFICATION_MODE_FULLSCREEN) { - boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, - canControlMagnification); - try { - return mController.getFullScreenMagnificationController().getCenterX(displayId); - } finally { - if (registeredJustForThisCall) { - unregister(displayId); - } + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, + canControlMagnification); + try { + return mController.getFullScreenMagnificationController().getCenterX(displayId); + } finally { + if (registeredJustForThisCall) { + unregister(displayId); } - } else if (mode == MAGNIFICATION_MODE_WINDOW) { - return mController.getWindowMagnificationMgr().getCenterX(displayId); } - return 0; } /** - * Returns the magnification center in Y coordinate of the controlling magnification mode. + * Returns the magnification center in Y coordinate of full-screen magnification. * If the service can control magnification but fullscreen magnifier is not registered, it will * register the magnifier for this call then unregister the magnifier finally to make the * magnification center correct. @@ -190,49 +190,25 @@ public class MagnificationProcessor { * @return the Y coordinate */ public float getCenterY(int displayId, boolean canControlMagnification) { - int mode = getControllingMode(displayId); - if (mode == MAGNIFICATION_MODE_FULLSCREEN) { - boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, - canControlMagnification); - try { - return mController.getFullScreenMagnificationController().getCenterY(displayId); - } finally { - if (registeredJustForThisCall) { - unregister(displayId); - } + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, + canControlMagnification); + try { + return mController.getFullScreenMagnificationController().getCenterY(displayId); + } finally { + if (registeredJustForThisCall) { + unregister(displayId); } - } else if (mode == MAGNIFICATION_MODE_WINDOW) { - return mController.getWindowMagnificationMgr().getCenterY(displayId); } - return 0; } /** - * Return the magnification bounds of the current controlling magnification on the given - * display. If the magnifier is not enabled, it returns an empty region. - * If the service can control magnification but fullscreen magnifier is not registered, it will - * register the magnifier for this call then unregister the magnifier finally to make - * the magnification region correct. + * Returns the magnification bounds of full-screen magnification on the given display. * * @param displayId The logical display id * @param outRegion the region to populate * @param canControlMagnification Whether the service can control magnification - * @return outRegion the magnification bounds of full-screen magnifier or the magnification - * source bounds of window magnifier */ - public Region getMagnificationRegion(int displayId, @NonNull Region outRegion, - boolean canControlMagnification) { - int mode = getControllingMode(displayId); - if (mode == MAGNIFICATION_MODE_FULLSCREEN) { - getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification); - } else if (mode == MAGNIFICATION_MODE_WINDOW) { - mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId, - outRegion); - } - return outRegion; - } - - private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion, + public void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion, boolean canControlMagnification) { boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, canControlMagnification); @@ -246,21 +222,9 @@ public class MagnificationProcessor { } } - private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale, - float centerX, float centerY, - boolean animate, int id) { - if (!isRegistered(displayId)) { - register(displayId); - } - return mController.getFullScreenMagnificationController().setScaleAndCenter( - displayId, - scale, - centerX, centerY, animate, id); - } - /** - * Resets the magnification on the given display. The reset mode could be full-screen or - * window if it is activated. + * Resets the current magnification on the given display. The reset mode could be + * full-screen or window if it is activated. * * @param displayId The logical display id. * @param animate {@code true} to animate the transition, {@code false} @@ -268,7 +232,7 @@ public class MagnificationProcessor { * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ - public boolean reset(int displayId, boolean animate) { + public boolean resetCurrentMagnification(int displayId, boolean animate) { int mode = getControllingMode(displayId); if (mode == MAGNIFICATION_MODE_FULLSCREEN) { return mController.getFullScreenMagnificationController().reset(displayId, animate); @@ -279,6 +243,19 @@ public class MagnificationProcessor { } /** + * Resets the full-screen magnification on the given display. + * + * @param displayId The logical display id. + * @param animate {@code true} to animate the transition, {@code false} + * to transition immediately + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change + */ + public boolean resetFullscreenMagnification(int displayId, boolean animate) { + return mController.getFullScreenMagnificationController().reset(displayId, animate); + } + + /** * {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)} */ // TODO: support window magnification diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b32d5434558b..9c996f452cf6 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -177,6 +177,7 @@ public class CompanionDeviceManagerService extends SystemService { new BluetoothDeviceConnectedListener(); private BleStateBroadcastReceiver mBleStateBroadcastReceiver = new BleStateBroadcastReceiver(); private List<String> mCurrentlyConnectedDevices = new ArrayList<>(); + Set<Integer> mPresentSelfManagedDevices = new HashSet<>(); private ArrayMap<String, Date> mDevicesLastNearby = new ArrayMap<>(); private UnbindDeviceListenersRunnable mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable(); @@ -217,7 +218,7 @@ public class CompanionDeviceManagerService extends SystemService { mPermissionControllerManager = requireNonNull( context.getSystemService(PermissionControllerManager.class)); mUserManager = context.getSystemService(UserManager.class); - mCompanionDevicePresenceController = new CompanionDevicePresenceController(); + mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); mAssociationRequestsProcessor = new AssociationRequestsProcessor(this); registerPackageMonitor(); @@ -555,6 +556,57 @@ public class CompanionDeviceManagerService extends SystemService { //TODO: b/199427116 } + @Override + public void notifyDeviceAppeared(int associationId) { + final AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (association == null) { + throw new IllegalArgumentException("Association with ID " + associationId + " " + + "does not exist " + + "or belongs to a different package " + + "or belongs to a different user"); + } + + if (!association.isSelfManaged()) { + throw new IllegalArgumentException("Association with ID " + associationId + + " is not self-managed. notifyDeviceAppeared(int) can only be called for" + + " self-managed associations."); + } + + if (!mPresentSelfManagedDevices.add(associationId)) { + Slog.w(LOG_TAG, "Association with ID " + associationId + " is already present"); + return; + } + + mCompanionDevicePresenceController.onDeviceNotifyAppeared( + association, getContext(), mMainHandler); + } + + @Override + public void notifyDeviceDisappeared(int associationId) { + final AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (association == null) { + throw new IllegalArgumentException("Association with ID " + associationId + " " + + "does not exist " + + "or belongs to a different package " + + "or belongs to a different user"); + } + + if (!association.isSelfManaged()) { + throw new IllegalArgumentException("Association with ID " + associationId + + " is not self-managed. notifyDeviceAppeared(int) can only be called for" + + " self-managed associations."); + } + + if (!mPresentSelfManagedDevices.contains(associationId)) { + Slog.w(LOG_TAG, "Association with ID " + associationId + " is not connected"); + return; + } + + mPresentSelfManagedDevices.remove(associationId); + mCompanionDevicePresenceController.onDeviceNotifyDisappearedAndUnbind( + association, getContext(), mMainHandler); + } + private void registerDevicePresenceListenerActive(String packageName, String deviceAddress, boolean active) throws RemoteException { getContext().enforceCallingOrSelfPermission( @@ -645,11 +697,18 @@ public class CompanionDeviceManagerService extends SystemService { } } + fout.append("Currently Connected Devices:").append('\n'); for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) { fout.append(" ").append(mCurrentlyConnectedDevices.get(i)).append('\n'); } + fout.append("Currently SelfManaged Connected Devices associationId:").append('\n'); + for (Integer associationId : mPresentSelfManagedDevices) { + fout.append(" ").append("AssociationId: ").append( + String.valueOf(associationId)).append('\n'); + } + fout.append("Devices Last Nearby:").append('\n'); for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) { String device = mDevicesLastNearby.keyAt(i); @@ -774,7 +833,9 @@ public class CompanionDeviceManagerService extends SystemService { } void onAssociationPreRemove(AssociationInfo association) { - if (association.isNotifyOnDeviceNearby()) { + if (association.isNotifyOnDeviceNearby() + || (association.isSelfManaged() + && mPresentSelfManagedDevices.contains(association.getId()))) { mCompanionDevicePresenceController.unbindDevicePresenceListener( association.getPackageName(), association.getUserId()); } diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java index 3e008467c9eb..444768491660 100644 --- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java +++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java @@ -49,8 +49,10 @@ public class CompanionDevicePresenceController { private static final String LOG_TAG = "CompanionDevicePresenceController"; PerUser<ArrayMap<String, List<BoundService>>> mBoundServices; private static final String META_DATA_KEY_PRIMARY = "primary"; + private final CompanionDeviceManagerService mService; - public CompanionDevicePresenceController() { + public CompanionDevicePresenceController(CompanionDeviceManagerService service) { + mService = service; mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() { @NonNull @Override @@ -61,24 +63,45 @@ public class CompanionDevicePresenceController { } void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) { - ServiceConnector<ICompanionDeviceService> primaryConnector = - getPrimaryServiceConnector(association, context, handler); - if (primaryConnector != null) { - Slog.i(LOG_TAG, - "Sending onDeviceAppeared to " + association.getPackageName() + ")"); - primaryConnector.run( - s -> s.onDeviceAppeared(association.getDeviceMacAddressAsString())); + for (BoundService boundService : getDeviceListenerServiceConnector( + association, context, handler)) { + if (boundService.mIsPrimary) { + Slog.i(LOG_TAG, + "Sending onDeviceAppeared to " + association.getPackageName() + ")"); + boundService.mServiceConnector.run( + service -> service.onDeviceAppeared(association)); + } else { + Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName); + boundService.mServiceConnector.connect(); + } } } void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) { - ServiceConnector<ICompanionDeviceService> primaryConnector = - getPrimaryServiceConnector(association, context, handler); - if (primaryConnector != null) { - Slog.i(LOG_TAG, - "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); - primaryConnector.run( - s -> s.onDeviceDisappeared(association.getDeviceMacAddressAsString())); + for (BoundService boundService : getDeviceListenerServiceConnector( + association, context, handler)) { + if (boundService.mIsPrimary) { + Slog.i(LOG_TAG, + "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); + boundService.mServiceConnector.run(service -> + service.onDeviceDisappeared(association)); + } + } + } + + void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association, + Context context, Handler handler) { + for (BoundService boundService : getDeviceListenerServiceConnector( + association, context, handler)) { + if (boundService.mIsPrimary) { + Slog.i(LOG_TAG, + "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); + boundService.mServiceConnector.post( + service -> { + service.onDeviceDisappeared(association); + }).thenRun(() -> unbindDevicePresenceListener( + association.getPackageName(), association.getUserId())); + } } } @@ -93,17 +116,6 @@ public class CompanionDevicePresenceController { } } - private ServiceConnector<ICompanionDeviceService> getPrimaryServiceConnector( - AssociationInfo association, Context context, Handler handler) { - for (BoundService boundService: getDeviceListenerServiceConnector(association, context, - handler)) { - if (boundService.mIsPrimary) { - return boundService.mServiceConnector; - } - } - return null; - } - private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context, Handler handler) { return mBoundServices.forUser(a.getUserId()).computeIfAbsent( @@ -140,18 +152,22 @@ public class CompanionDevicePresenceController { protected long getAutoDisconnectTimeoutMs() { // Service binding is managed manually based on corresponding device // being nearby - return Long.MAX_VALUE; + return -1; } @Override public void binderDied() { super.binderDied(); - - // Re-connect to the service if process gets killed - handler.postDelayed( - this::connect, - CompanionDeviceManagerService - .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS); + if (a.isSelfManaged()) { + mBoundServices.forUser(a.getUserId()).remove(a.getPackageName()); + mService.mPresentSelfManagedDevices.remove(a.getId()); + } else { + // Re-connect to the service if process gets killed + handler.postDelayed( + this::connect, + CompanionDeviceManagerService + .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS); + } } }; @@ -191,6 +207,13 @@ public class CompanionDevicePresenceController { } } + if (packageResolveInfos.size() > 1 && primaryCount == 0) { + Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService " + + "to be bound when declare more than one CompanionDeviceService but " + + association.getPackageName() + " has " + primaryCount); + return false; + } + if (packageResolveInfos.size() == 1 && primaryCount != 0) { Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one" + " CompanionDeviceService " + "but " + association.getPackageName() diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java new file mode 100644 index 000000000000..067edcc0b08d --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import android.annotation.NonNull; +import android.graphics.Point; +import android.hardware.input.VirtualKeyEvent; +import android.hardware.input.VirtualMouseButtonEvent; +import android.hardware.input.VirtualMouseRelativeEvent; +import android.hardware.input.VirtualMouseScrollEvent; +import android.hardware.input.VirtualTouchEvent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; +import java.util.Map; + +/** Controls virtual input devices, including device lifecycle and event dispatch. */ +class InputController { + + private final Object mLock; + + /* Token -> file descriptor associations. */ + @VisibleForTesting + @GuardedBy("mLock") + final Map<IBinder, Integer> mInputDeviceFds = new ArrayMap<>(); + + private final NativeWrapper mNativeWrapper; + + InputController(@NonNull Object lock) { + this(lock, new NativeWrapper()); + } + + @VisibleForTesting + InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) { + mLock = lock; + mNativeWrapper = nativeWrapper; + } + + void close() { + synchronized (mLock) { + for (int fd : mInputDeviceFds.values()) { + mNativeWrapper.closeUinput(fd); + } + mInputDeviceFds.clear(); + } + } + + void createKeyboard(@NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken) { + final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId); + if (fd < 0) { + throw new RuntimeException( + "A native error occurred when creating keyboard: " + -fd); + } + synchronized (mLock) { + mInputDeviceFds.put(deviceToken, fd); + } + try { + deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + } catch (RemoteException e) { + throw new RuntimeException("Could not create virtual keyboard", e); + } + } + + void createMouse(@NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken) { + final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId); + if (fd < 0) { + throw new RuntimeException( + "A native error occurred when creating mouse: " + -fd); + } + synchronized (mLock) { + mInputDeviceFds.put(deviceToken, fd); + } + try { + deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + } catch (RemoteException e) { + throw new RuntimeException("Could not create virtual mouse", e); + } + } + + void createTouchscreen(@NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken, + @NonNull Point screenSize) { + final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, + screenSize.y, screenSize.x); + if (fd < 0) { + throw new RuntimeException( + "A native error occurred when creating touchscreen: " + -fd); + } + synchronized (mLock) { + mInputDeviceFds.put(deviceToken, fd); + } + try { + deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + } catch (RemoteException e) { + throw new RuntimeException("Could not create virtual touchscreen", e); + } + } + + void unregisterInputDevice(@NonNull IBinder token) { + synchronized (mLock) { + final Integer fd = mInputDeviceFds.remove(token); + if (fd == null) { + throw new IllegalArgumentException( + "Could not unregister input device for given token"); + } + mNativeWrapper.closeUinput(fd); + } + } + + boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { + synchronized (mLock) { + final Integer fd = mInputDeviceFds.get(token); + if (fd == null) { + throw new IllegalArgumentException( + "Could not send key event to input device for given token"); + } + return mNativeWrapper.writeKeyEvent(fd, event.getKeyCode(), event.getAction()); + } + } + + boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) { + synchronized (mLock) { + final Integer fd = mInputDeviceFds.get(token); + if (fd == null) { + throw new IllegalArgumentException( + "Could not send button event to input device for given token"); + } + return mNativeWrapper.writeButtonEvent(fd, event.getButtonCode(), event.getAction()); + } + } + + boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) { + synchronized (mLock) { + final Integer fd = mInputDeviceFds.get(token); + if (fd == null) { + throw new IllegalArgumentException( + "Could not send touch event to input device for given token"); + } + return mNativeWrapper.writeTouchEvent(fd, event.getPointerId(), event.getToolType(), + event.getAction(), event.getX(), event.getY(), event.getPressure(), + event.getMajorAxisSize()); + } + } + + boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) { + synchronized (mLock) { + final Integer fd = mInputDeviceFds.get(token); + if (fd == null) { + throw new IllegalArgumentException( + "Could not send relative event to input device for given token"); + } + return mNativeWrapper.writeRelativeEvent(fd, event.getRelativeX(), + event.getRelativeY()); + } + } + + boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) { + synchronized (mLock) { + final Integer fd = mInputDeviceFds.get(token); + if (fd == null) { + throw new IllegalArgumentException( + "Could not send scroll event to input device for given token"); + } + return mNativeWrapper.writeScrollEvent(fd, event.getXAxisMovement(), + event.getYAxisMovement()); + } + } + + public void dump(@NonNull PrintWriter fout) { + fout.println(" InputController: "); + synchronized (mLock) { + fout.println(" Active file descriptors: "); + for (int inputDeviceFd : mInputDeviceFds.values()) { + fout.println(inputDeviceFd); + } + } + } + + private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId, + int productId); + private static native int nativeOpenUinputMouse(String deviceName, int vendorId, + int productId); + private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId, + int productId, int height, int width); + private static native boolean nativeCloseUinput(int fd); + private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action); + private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action); + private static native boolean nativeWriteTouchEvent(int fd, int pointerId, int toolType, + int action, float locationX, float locationY, float pressure, float majorAxisSize); + private static native boolean nativeWriteRelativeEvent(int fd, float relativeX, + float relativeY); + private static native boolean nativeWriteScrollEvent(int fd, float xAxisMovement, + float yAxisMovement); + + /** Wrapper around the static native methods for tests. */ + @VisibleForTesting + protected static class NativeWrapper { + public int openUinputKeyboard(String deviceName, int vendorId, int productId) { + return nativeOpenUinputKeyboard(deviceName, vendorId, + productId); + } + + public int openUinputMouse(String deviceName, int vendorId, int productId) { + return nativeOpenUinputMouse(deviceName, vendorId, + productId); + } + + public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height, + int width) { + return nativeOpenUinputTouchscreen(deviceName, vendorId, + productId, height, width); + } + + public boolean closeUinput(int fd) { + return nativeCloseUinput(fd); + } + + public boolean writeKeyEvent(int fd, int androidKeyCode, int action) { + return nativeWriteKeyEvent(fd, androidKeyCode, action); + } + + public boolean writeButtonEvent(int fd, int buttonCode, int action) { + return nativeWriteButtonEvent(fd, buttonCode, action); + } + + public boolean writeTouchEvent(int fd, int pointerId, int toolType, int action, + float locationX, float locationY, float pressure, float majorAxisSize) { + return nativeWriteTouchEvent(fd, pointerId, toolType, + action, locationX, locationY, + pressure, majorAxisSize); + } + + public boolean writeRelativeEvent(int fd, float relativeX, float relativeY) { + return nativeWriteRelativeEvent(fd, relativeX, relativeY); + } + + public boolean writeScrollEvent(int fd, float xAxisMovement, float yAxisMovement) { + return nativeWriteScrollEvent(fd, xAxisMovement, + yAxisMovement); + } + } + + private final class BinderDeathRecipient implements IBinder.DeathRecipient { + + private final IBinder mDeviceToken; + + BinderDeathRecipient(IBinder deviceToken) { + mDeviceToken = deviceToken; + } + + @Override + public void binderDied() { + unregisterInputDevice(mDeviceToken); + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java new file mode 100644 index 000000000000..022da4361be4 --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import android.annotation.NonNull; +import android.companion.AssociationInfo; +import android.companion.virtual.IVirtualDevice; +import android.content.Context; +import android.graphics.Point; +import android.hardware.input.VirtualKeyEvent; +import android.hardware.input.VirtualMouseButtonEvent; +import android.hardware.input.VirtualMouseRelativeEvent; +import android.hardware.input.VirtualMouseScrollEvent; +import android.hardware.input.VirtualTouchEvent; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.window.DisplayWindowPolicyController; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + + +final class VirtualDeviceImpl extends IVirtualDevice.Stub + implements IBinder.DeathRecipient { + + private final Object mVirtualDeviceLock = new Object(); + + private final Context mContext; + private final AssociationInfo mAssociationInfo; + private final int mOwnerUid; + private final GenericWindowPolicyController mGenericWindowPolicyController; + private final InputController mInputController; + @VisibleForTesting + final List<Integer> mVirtualDisplayIds = new ArrayList<>(); + private final OnDeviceCloseListener mListener; + + VirtualDeviceImpl(Context context, AssociationInfo associationInfo, + IBinder token, int ownerUid, OnDeviceCloseListener listener) { + this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener); + } + + @VisibleForTesting + VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token, + int ownerUid, InputController inputController, OnDeviceCloseListener listener) { + mContext = context; + mAssociationInfo = associationInfo; + mGenericWindowPolicyController = new GenericWindowPolicyController(FLAG_SECURE, + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + mOwnerUid = ownerUid; + if (inputController == null) { + mInputController = new InputController(mVirtualDeviceLock); + } else { + mInputController = inputController; + } + mListener = listener; + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getAssociationId() { + return mAssociationInfo.getId(); + } + + @Override // Binder call + public void close() { + mListener.onClose(mAssociationInfo.getId()); + mInputController.close(); + } + + @Override + public void binderDied() { + close(); + } + + @Override // Binder call + public void createVirtualKeyboard( + int displayId, + @NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to create a virtual keyboard"); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new SecurityException( + "Cannot create a virtual keyboard for a display not associated with " + + "this virtual device"); + } + } + final long token = Binder.clearCallingIdentity(); + try { + mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void createVirtualMouse( + int displayId, + @NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to create a virtual mouse"); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new SecurityException( + "Cannot create a virtual mouse for a display not associated with this " + + "virtual device"); + } + } + final long token = Binder.clearCallingIdentity(); + try { + mInputController.createMouse(deviceName, vendorId, productId, deviceToken); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void createVirtualTouchscreen( + int displayId, + @NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken, + @NonNull Point screenSize) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to create a virtual touchscreen"); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new SecurityException( + "Cannot create a virtual touchscreen for a display not associated with " + + "this virtual device"); + } + } + final long token = Binder.clearCallingIdentity(); + try { + mInputController.createTouchscreen(deviceName, vendorId, productId, + deviceToken, screenSize); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void unregisterInputDevice(IBinder token) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to unregister this input device"); + + final long binderToken = Binder.clearCallingIdentity(); + try { + mInputController.unregisterInputDevice(token); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override // Binder call + public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.sendKeyEvent(token, event); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override // Binder call + public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.sendButtonEvent(token, event); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override // Binder call + public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.sendTouchEvent(token, event); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override // Binder call + public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.sendRelativeEvent(token, event); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override // Binder call + public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.sendScrollEvent(token, event); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + fout.println(" VirtualDevice: "); + fout.println(" mVirtualDisplayIds: "); + synchronized (mVirtualDeviceLock) { + for (int id : mVirtualDisplayIds) { + fout.println(" " + id); + } + } + mInputController.dump(fout); + } + + DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) { + if (mVirtualDisplayIds.contains(displayId)) { + throw new IllegalStateException( + "Virtual device already have a virtual display with ID " + displayId); + } + mVirtualDisplayIds.add(displayId); + return mGenericWindowPolicyController; + } + + void onVirtualDisplayRemovedLocked(int displayId) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new IllegalStateException( + "Virtual device doesn't have a virtual display with ID " + displayId); + } + mVirtualDisplayIds.remove(displayId); + } + + int getOwnerUid() { + return mOwnerUid; + } + + interface OnDeviceCloseListener { + void onClose(int associationId); + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 27426089f409..46e75f75ea9f 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -16,9 +16,6 @@ package com.android.server.companion.virtual; -import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -42,7 +39,6 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -52,6 +48,7 @@ public class VirtualDeviceManagerService extends SystemService { private static final boolean DEBUG = false; private static final String LOG_TAG = "VirtualDeviceManagerService"; + private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; @@ -130,64 +127,9 @@ public class VirtualDeviceManagerService extends SystemService { } } - private class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient { - - private final AssociationInfo mAssociationInfo; - private final int mOwnerUid; - private final GenericWindowPolicyController mGenericWindowPolicyController; - private final ArrayList<Integer> mDisplayIds = new ArrayList<>(); - - private VirtualDeviceImpl(int ownerUid, IBinder token, AssociationInfo associationInfo) { - mOwnerUid = ownerUid; - mAssociationInfo = associationInfo; - mGenericWindowPolicyController = new GenericWindowPolicyController(FLAG_SECURE, - SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); - try { - token.linkToDeath(this, 0); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - mVirtualDevices.put(associationInfo.getId(), this); - } - - @Override - public int getAssociationId() { - return mAssociationInfo.getId(); - } - - @Override - public void close() { - synchronized (mVirtualDeviceManagerLock) { - mVirtualDevices.remove(mAssociationInfo.getId()); - } - } - - @Override - public void binderDied() { - close(); - } - - DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) { - if (mDisplayIds.contains(displayId)) { - throw new IllegalStateException( - "Virtual device already have a virtual display with ID " + displayId); - } - mDisplayIds.add(displayId); - return mGenericWindowPolicyController; - } - - void onVirtualDisplayRemovedLocked(int displayId) { - if (!mDisplayIds.contains(displayId)) { - throw new IllegalStateException( - "Virtual device doesn't have a virtual display with ID " + displayId); - } - mDisplayIds.remove(displayId); - } - } - class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub { - @Override + @Override // Binder call public IVirtualDevice createVirtualDevice( IBinder token, String packageName, int associationId) { getContext().enforceCallingOrSelfPermission( @@ -209,7 +151,18 @@ public class VirtualDeviceManagerService extends SystemService { "Virtual device for association ID " + associationId + " already exists"); } - return new VirtualDeviceImpl(callingUid, token, associationInfo); + VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), + associationInfo, token, callingUid, + new VirtualDeviceImpl.OnDeviceCloseListener() { + @Override + public void onClose(int associationId) { + synchronized (mVirtualDeviceManagerLock) { + mVirtualDevices.remove(associationId); + } + } + }); + mVirtualDevices.put(associationInfo.getId(), virtualDevice); + return virtualDevice; } } @@ -254,8 +207,7 @@ public class VirtualDeviceManagerService extends SystemService { fout.println("Created virtual devices: "); synchronized (mVirtualDeviceManagerLock) { for (int i = 0; i < mVirtualDevices.size(); i++) { - VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); - fout.printf("%d: %s\n", mVirtualDevices.keyAt(i), virtualDevice); + mVirtualDevices.valueAt(i).dump(fd, fout, args); } } } @@ -290,7 +242,7 @@ public class VirtualDeviceManagerService extends SystemService { synchronized (mVirtualDeviceManagerLock) { int size = mVirtualDevices.size(); for (int i = 0; i < size; i++) { - if (mVirtualDevices.valueAt(i).mOwnerUid == uid) { + if (mVirtualDevices.valueAt(i).getOwnerUid() == uid) { return true; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a33aa600febd..a745e5afc6f0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15659,6 +15659,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @Override + public void enableBinderTracing() { + Binder.enableTracingForUid(Binder.getCallingUid()); + } + @VisibleForTesting public final class LocalService extends ActivityManagerInternal implements ActivityManagerLocal { diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 20606f0e9f4d..8dce8e9e90ef 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -385,9 +385,8 @@ public final class GameManagerService extends IGameManagerService.Stub { } public boolean isValid() { - return (mGameMode == GameManager.GAME_MODE_PERFORMANCE - || mGameMode == GameManager.GAME_MODE_BATTERY) - && (!mAllowDownscale || getCompatChangeId() != 0); + return mGameMode == GameManager.GAME_MODE_PERFORMANCE + || mGameMode == GameManager.GAME_MODE_BATTERY; } /** @@ -839,7 +838,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } long scaleId = modeConfig.getCompatChangeId(); if (scaleId == 0) { - Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for " + Slog.i(TAG, "Invalid downscaling change id " + scaleId + " for " + packageName); return; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6238198e9488..f857064a065a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -10142,6 +10142,27 @@ public class AudioService extends IAudioService.Stub return AudioManager.SUCCESS; } + /** @see AudioPolicy#getFocusStack() */ + public List<AudioFocusInfo> getFocusStack() { + enforceModifyAudioRoutingPermission(); + return mMediaFocusControl.getFocusStack(); + } + + /** @see AudioPolicy#sendFocusLoss */ + public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser, + @NonNull IAudioPolicyCallback apcb) { + Objects.requireNonNull(focusLoser); + Objects.requireNonNull(apcb); + enforceModifyAudioRoutingPermission(); + if (!mAudioPolicies.containsKey(apcb.asBinder())) { + throw new IllegalStateException("Only registered AudioPolicy can change focus"); + } + if (!mAudioPolicies.get(apcb.asBinder()).mHasFocusListener) { + throw new IllegalStateException("AudioPolicy must have focus listener to change focus"); + } + return mMediaFocusControl.sendFocusLoss(focusLoser); + } + /** see AudioManager.hasRegisteredDynamicPolicy */ public boolean hasRegisteredDynamicPolicy() { synchronized (mAudioPolicies) { diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 66f62355c7f0..69a4c23cf867 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -27,6 +27,7 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioFocusDispatcher; import android.media.MediaMetrics; +import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.IAudioPolicyCallback; import android.os.Binder; import android.os.Build; @@ -221,6 +222,51 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } } + /** + * Return a copy of the focus stack for external consumption (composed of AudioFocusInfo + * instead of FocusRequester instances) + * @return a SystemApi-friendly version of the focus stack, in the same order (last entry + * is top of focus stack, i.e. latest focus owner) + * @see AudioPolicy#getFocusStack() + */ + @NonNull List<AudioFocusInfo> getFocusStack() { + synchronized (mAudioFocusLock) { + final ArrayList<AudioFocusInfo> stack = new ArrayList<>(mFocusStack.size()); + for (FocusRequester fr : mFocusStack) { + stack.add(fr.toAudioFocusInfo()); + } + return stack; + } + } + + /** + * Send AUDIOFOCUS_LOSS to a specific stack entry. + * Note this method is supporting an external API, and is restricted to LOSS in order to + * prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus) + * @param focusLoser the stack entry that is exiting the stack through a focus loss + * @return false if the focusLoser wasn't found in the stack, true otherwise + * @see AudioPolicy#sendFocusLoss(AudioFocusInfo) + */ + boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) { + synchronized (mAudioFocusLock) { + FocusRequester loserToRemove = null; + for (FocusRequester fr : mFocusStack) { + if (fr.getClientId().equals(focusLoser.getClientId())) { + fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, + false /*forceDuck*/); + loserToRemove = fr; + break; + } + } + if (loserToRemove != null) { + mFocusStack.remove(loserToRemove); + loserToRemove.release(); + return true; + } + } + return false; + } + @GuardedBy("mAudioFocusLock") private void notifyTopOfAudioFocusStack() { // notify the top of the stack it gained focus diff --git a/services/core/java/com/android/server/communal/OWNERS b/services/core/java/com/android/server/communal/OWNERS index e499b160beae..b02883da854a 100644 --- a/services/core/java/com/android/server/communal/OWNERS +++ b/services/core/java/com/android/server/communal/OWNERS @@ -1,3 +1,4 @@ brycelee@google.com justinkoh@google.com -lusilva@google.com
\ No newline at end of file +lusilva@google.com +xilei@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 05e1bdd11db6..3d91feef7043 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -39,6 +39,7 @@ import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; +import android.util.EventLog; import android.util.Slog; import android.view.IWindowManager; import android.view.WindowManager; @@ -49,6 +50,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.UnbindReason; import com.android.internal.view.IInputMethod; +import com.android.server.EventLogTags; import com.android.server.wm.WindowManagerInternal; /** @@ -58,6 +60,9 @@ final class InputMethodBindingController { static final boolean DEBUG = false; private static final String TAG = InputMethodBindingController.class.getSimpleName(); + /** Time in milliseconds that the IME service has to bind before it is reconnected. */ + static final long TIME_TO_RECONNECT = 3 * 1000; + @NonNull private final InputMethodManagerService mService; @NonNull private final Context mContext; @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap; @@ -239,7 +244,7 @@ final class InputMethodBindingController { } /** - * Indicates whether {@link #getVisibleConnection} is currently in use. + * Indicates whether {@link #mVisibleConnection} is currently in use. */ boolean isVisibleBound() { return mVisibleBound; @@ -248,11 +253,6 @@ final class InputMethodBindingController { /** * Used to bring IME service up to visible adjustment while it is being shown. */ - @NonNull - ServiceConnection getVisibleConnection() { - return mVisibleConnection; - } - private final ServiceConnection mVisibleConnection = new ServiceConnection() { @Override public void onBindingDied(ComponentName name) { synchronized (mMethodMap) { @@ -334,7 +334,7 @@ final class InputMethodBindingController { // We consider this to be a new bind attempt, since the system // should now try to restart the service for us. mLastBindTime = SystemClock.uptimeMillis(); - mService.clearClientSessionsLocked(); + clearCurMethodAndSessionsLocked(); mService.clearInputShowRequestLocked(); mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); } @@ -358,11 +358,12 @@ final class InputMethodBindingController { } mCurId = null; - mService.clearClientSessionsLocked(); + clearCurMethodAndSessionsLocked(); } @GuardedBy("mMethodMap") - void clearCurMethodLocked() { + private void clearCurMethodAndSessionsLocked() { + mService.clearClientSessionsLocked(); mCurMethod = null; mCurMethodUid = Process.INVALID_UID; } @@ -382,7 +383,7 @@ final class InputMethodBindingController { @GuardedBy("mMethodMap") @NonNull - InputBindResult bindCurrentMethodLocked(int displayIdToShowIme) { + InputBindResult bindCurrentMethodLocked() { InputMethodInfo info = mMethodMap.get(mSelectedMethodId); if (info == null) { throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId); @@ -394,7 +395,7 @@ final class InputMethodBindingController { mCurId = info.getId(); mLastBindTime = SystemClock.uptimeMillis(); - addFreshWindowTokenLocked(displayIdToShowIme); + addFreshWindowTokenLocked(); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, null, null, mCurId, mCurSeq, false); @@ -419,7 +420,8 @@ final class InputMethodBindingController { } @GuardedBy("mMethodMap") - private void addFreshWindowTokenLocked(int displayIdToShowIme) { + private void addFreshWindowTokenLocked() { + int displayIdToShowIme = mService.getDisplayIdToShowIme(); mCurToken = new Binder(); mService.setCurTokenDisplayId(displayIdToShowIme); @@ -438,7 +440,7 @@ final class InputMethodBindingController { } @GuardedBy("mMethodMap") - void unbindMainConnectionLocked() { + private void unbindMainConnectionLocked() { mContext.unbindService(mMainConnection); mHasConnection = false; } @@ -460,17 +462,61 @@ final class InputMethodBindingController { } @GuardedBy("mMethodMap") - boolean bindCurrentInputMethodServiceVisibleConnectionLocked() { + private boolean bindCurrentInputMethodServiceVisibleConnectionLocked() { mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection, IME_VISIBLE_BIND_FLAGS); return mVisibleBound; } @GuardedBy("mMethodMap") - boolean bindCurrentInputMethodServiceMainConnectionLocked() { + private boolean bindCurrentInputMethodServiceMainConnectionLocked() { mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection, mImeConnectionBindFlags); return mHasConnection; } + /** + * Bind the IME so that it can be shown. + * + * <p> + * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds. + */ + @GuardedBy("mMethodMap") + void setCurrentMethodVisibleLocked() { + if (mCurMethod != null) { + if (DEBUG) Slog.d(TAG, "setCurrentMethodVisibleLocked: mCurToken=" + mCurToken); + if (mHasConnection && !mVisibleBound) { + bindCurrentInputMethodServiceVisibleConnectionLocked(); + } + return; + } + + long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime; + if (mHasConnection && bindingDuration >= TIME_TO_RECONNECT) { + // The client has asked to have the input method shown, but + // we have been sitting here too long with a connection to the + // service and no interface received, so let's disconnect/connect + // to try to prod things along. + EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(), + bindingDuration, 1); + Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisibleLocked()"); + unbindMainConnectionLocked(); + bindCurrentInputMethodServiceMainConnectionLocked(); + } else { + if (DEBUG) { + Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = " + + (TIME_TO_RECONNECT - bindingDuration)); + } + } + } + + /** + * Remove the binding needed for the IME to be shown. + */ + @GuardedBy("mMethodMap") + void setCurrentMethodNotVisibleLocked() { + if (mVisibleBound) { + unbindVisibleConnectionLocked(); + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3c6b0966dfc3..bff4f273e195 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -49,6 +49,8 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; +import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT; + import static java.lang.annotation.RetentionPolicy.SOURCE; import android.Manifest; @@ -111,12 +113,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; -import android.text.style.SuggestionSpan; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; import android.util.IndentingPrintWriter; -import android.util.LruCache; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -250,10 +250,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; - static final long TIME_TO_RECONNECT = 3 * 1000; - - static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20; - private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; private static final String HANDLER_THREAD_NAME = "android.imms"; @@ -301,8 +297,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // lock for this class. final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>(); final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>(); - private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = - new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE); final InputMethodSubtypeSwitchingController mSwitchingController; /** @@ -312,13 +306,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private int mMethodMapUpdateCount = 0; /** - * Indicates whether {@link InputMethodBindingController#getVisibleConnection} is currently - * in use. + * The display id for which the latest startInput was called. */ - private boolean isVisibleBound() { - return mBindingController.isVisibleBound(); + @GuardedBy("mMethodMap") + int getDisplayIdToShowIme() { + return mDisplayIdToShowIme; } + @GuardedBy("mMethodMap") + private int mDisplayIdToShowIme = INVALID_DISPLAY; + // Ongoing notification private NotificationManager mNotificationManager; KeyguardManager mKeyguardManager; @@ -2354,10 +2351,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. - final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId, + mDisplayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId, mImeDisplayValidator); - if (displayIdToShowIme == INVALID_DISPLAY) { + if (mDisplayIdToShowIme == INVALID_DISPLAY) { mImeHiddenByDisplayPolicy = true; hideCurrentInputLocked(mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE); @@ -2378,7 +2375,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Check if the input method is changing. // We expect the caller has already verified that the client is allowed to access this // display ID. - if (isSelectedMethodBound(displayIdToShowIme)) { + if (isSelectedMethodBound()) { if (cs.curSession != null) { // Fast case: if we are already connected to the input method, // then just return it. @@ -2394,13 +2391,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mBindingController.unbindCurrentMethodLocked(); - return mBindingController.bindCurrentMethodLocked(displayIdToShowIme); + return mBindingController.bindCurrentMethodLocked(); } - private boolean isSelectedMethodBound(int displayIdToShowIme) { + private boolean isSelectedMethodBound() { String curId = getCurId(); return curId != null && curId.equals(getSelectedMethodId()) - && displayIdToShowIme == mCurTokenDisplayId; + && mDisplayIdToShowIme == mCurTokenDisplayId; } @GuardedBy("mMethodMap") @@ -2590,7 +2587,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub finishSessionLocked(mEnabledSession); mEnabledSession = null; - mBindingController.clearCurMethodLocked(); scheduleNotifyImeUidToAudioService(Process.INVALID_UID); } hideStatusBarIconLocked(); @@ -3047,42 +3043,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return false; } - boolean res = false; - IInputMethod curMethod = getCurMethod(); - if (curMethod != null) { - if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + getCurToken()); + mBindingController.setCurrentMethodVisibleLocked(); + if (getCurMethod() != null) { // create a placeholder token for IMS so that IMS cannot inject windows into client app. Binder showInputToken = new Binder(); mShowRequestWindowMap.put(showInputToken, windowToken); + IInputMethod curMethod = getCurMethod(); executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT, getImeShowFlagsLocked(), reason, curMethod, resultReceiver, showInputToken)); mInputShown = true; - if (hasConnection() && !isVisibleBound()) { - mBindingController.bindCurrentInputMethodServiceVisibleConnectionLocked(); - } - res = true; - } else { - long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime(); - if (hasConnection() && bindingDuration >= TIME_TO_RECONNECT) { - // The client has asked to have the input method shown, but - // we have been sitting here too long with a connection to the - // service and no interface received, so let's disconnect/connect - // to try to prod things along. - EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(), - bindingDuration, 1); - Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()"); - mBindingController.unbindMainConnectionLocked(); - mBindingController.bindCurrentInputMethodServiceMainConnectionLocked(); - } else { - if (DEBUG) { - Slog.d(TAG, "Can't show input: connection = " + hasConnection() + ", time = " - + (TIME_TO_RECONNECT - bindingDuration)); - } - } + return true; } - return res; + return false; } @Override @@ -3166,9 +3140,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } else { res = false; } - if (hasConnection() && isVisibleBound()) { - mBindingController.unbindVisibleConnectionLocked(); - } + mBindingController.setCurrentMethodNotVisibleLocked(); mInputShown = false; mShowRequested = false; mShowExplicitlyRequested = false; @@ -3522,10 +3494,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mWindowManagerInternal.shouldRestoreImeVisibility(windowToken); } - private boolean isImeVisible() { - return (mImeWindowVis & InputMethodService.IME_VISIBLE) != 0; - } - @GuardedBy("mMethodMap") private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { // TODO(yukawa): multi-display support. @@ -5114,7 +5082,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " client=" + mCurFocusedWindowClient); focusedWindowClient = mCurFocusedWindowClient; p.println(" mCurId=" + getCurId() + " mHaveConnection=" + hasConnection() - + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + isVisibleBound()); + + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + + mBindingController.isVisibleBound()); p.println(" mCurToken=" + getCurToken()); p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId); p.println(" mCurHostInputToken=" + mCurHostInputToken); diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 0ce24dda666e..ede8b32c9b11 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -177,7 +177,7 @@ public class LocationProviderManager extends protected interface LocationTransport { void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) throws Exception; + @Nullable IRemoteCallback onCompleteCallback) throws Exception; void deliverOnFlushComplete(int requestCode) throws Exception; } @@ -197,9 +197,8 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) throws RemoteException { - mListener.onLocationChanged(locationResult.asList(), - SingleUseCallback.wrap(onCompleteCallback)); + @Nullable IRemoteCallback onCompleteCallback) throws RemoteException { + mListener.onLocationChanged(locationResult.asList(), onCompleteCallback); } @Override @@ -227,7 +226,7 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) + @Nullable IRemoteCallback onCompleteCallback) throws PendingIntent.CanceledException { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setDontSendToRestrictedApps(true); @@ -243,20 +242,34 @@ public class LocationProviderManager extends intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0])); } + PendingIntent.OnFinished onFinished = null; + // send() SHOULD only run the completion callback if it completes successfully. however, - // b/199464864 (which could not be fixed in the S timeframe) means that it's possible + // b/201299281 (which could not be fixed in the S timeframe) means that it's possible // for send() to throw an exception AND run the completion callback. if this happens, we // would over-release the wakelock... we take matters into our own hands to ensure that // the completion callback can only be run if send() completes successfully. this means // the completion callback may be run inline - but as we've never specified what thread // the callback is run on, this is fine. - GatedCallback gatedCallback = new GatedCallback(onCompleteCallback); + GatedCallback gatedCallback; + if (onCompleteCallback != null) { + gatedCallback = new GatedCallback(() -> { + try { + onCompleteCallback.sendResult(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + }); + onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run(); + } else { + gatedCallback = new GatedCallback(null); + } mPendingIntent.send( mContext, 0, intent, - (pI, i, rC, rD, rE) -> gatedCallback.run(), + onFinished, null, null, options.toBundle()); @@ -293,7 +306,7 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(@Nullable LocationResult locationResult, - @Nullable Runnable onCompleteCallback) + @Nullable IRemoteCallback onCompleteCallback) throws RemoteException { // ILocationCallback doesn't currently support completion callbacks Preconditions.checkState(onCompleteCallback == null); @@ -714,6 +727,13 @@ public class LocationProviderManager extends final PowerManager.WakeLock mWakeLock; + // b/206340085 - if we allocate a new wakelock releaser object for every delivery we + // increase the risk of resource starvation. if a client stops processing deliveries the + // system server binder allocation pool will be starved as we continue to queue up + // deliveries, each with a new allocation. in order to mitigate this, we use a single + // releaser object per registration rather than per delivery. + final ExternalWakeLockReleaser mWakeLockReleaser; + private volatile ProviderTransport mProviderTransport; private int mNumLocationsDelivered = 0; private long mExpirationRealtimeMs = Long.MAX_VALUE; @@ -727,6 +747,7 @@ public class LocationProviderManager extends .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); mWakeLock.setReferenceCounted(true); mWakeLock.setWorkSource(request.getWorkSource()); + mWakeLockReleaser = new ExternalWakeLockReleaser(identity, mWakeLock); } @Override @@ -943,7 +964,7 @@ public class LocationProviderManager extends } listener.deliverOnLocationChanged(deliverLocationResult, - mUseWakeLock ? mWakeLock::release : null); + mUseWakeLock ? mWakeLockReleaser : null); EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(), getIdentity()); } @@ -2761,7 +2782,7 @@ public class LocationProviderManager extends @GuardedBy("this") private boolean mRun; - GatedCallback(Runnable callback) { + GatedCallback(@Nullable Runnable callback) { mCallback = callback; } @@ -2796,4 +2817,24 @@ public class LocationProviderManager extends } } } + + private static class ExternalWakeLockReleaser extends IRemoteCallback.Stub { + + private final CallerIdentity mIdentity; + private final PowerManager.WakeLock mWakeLock; + + ExternalWakeLockReleaser(CallerIdentity identity, PowerManager.WakeLock wakeLock) { + mIdentity = identity; + mWakeLock = Objects.requireNonNull(wakeLock); + } + + @Override + public void sendResult(Bundle data) { + try { + mWakeLock.release(); + } catch (RuntimeException e) { + Log.e(TAG, "wakelock over-released by " + mIdentity, e); + } + } + } } diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index 99cb6f03e6da..24008d0f64ca 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -167,6 +167,7 @@ public final class PermissionHelper { public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant, boolean userSet) { assertFlag(); + final long callingId = Binder.clearCallingIdentity(); try { if (grant) { mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId); @@ -180,13 +181,20 @@ public final class PermissionHelper { } } catch (RemoteException e) { Slog.e(TAG, "Could not reach system server", e); + } finally { + Binder.restoreCallingIdentity(callingId); } } public void setNotificationPermission(PackagePermission pkgPerm) { assertFlag(); - setNotificationPermission( - pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet); + final long callingId = Binder.clearCallingIdentity(); + try { + setNotificationPermission( + pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet); + } finally { + Binder.restoreCallingIdentity(callingId); + } } public boolean isPermissionFixed(String packageName, @UserIdInt int userId) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 0564e858c84c..fdcf1fcfd260 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; +import android.Manifest; import android.accounts.IAccountManager; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -170,6 +171,20 @@ class PackageManagerShellCommand extends ShellCommand { @Override public int onCommand(String cmd) { + switch (Binder.getCallingUid()) { + case Process.ROOT_UID: + case Process.SHELL_UID: + break; + default: + // This is called from a test and is allowed as non-shell with the right permission + if ("install-incremental".equals(cmd)) { + mContext.enforceCallingPermission(Manifest.permission.USE_SYSTEM_DATA_LOADERS, + "Caller missing USE_SYSTEM_DATA_LOADERS permission to use " + cmd); + } else { + throw new IllegalArgumentException("Caller must be root or shell"); + } + } + if (cmd == null) { return handleDefaultCommands(cmd); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f6895195781c..167ad3ba7d0e 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -839,7 +839,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated) { - checkManageOrCreateUsersPermission("query users"); + checkCreateUsersPermission("query users"); return getUsersInternal(excludePartial, excludeDying, excludePreCreated); } @@ -868,7 +868,7 @@ public class UserManagerService extends IUserManager.Stub { checkQueryOrCreateUsersPermission("getting profiles related to user " + userId); returnFullInfo = true; } else { - returnFullInfo = hasManageOrCreateUsersPermission(); + returnFullInfo = hasCreateUsersPermission(); } final long ident = Binder.clearCallingIdentity(); try { @@ -1708,7 +1708,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean isRestricted(@UserIdInt int userId) { if (userId != UserHandle.getCallingUserId()) { - checkManageOrCreateUsersPermission("query isRestricted for user " + userId); + checkCreateUsersPermission("query isRestricted for user " + userId); } synchronized (mUsersLock) { final UserInfo userInfo = getUserInfoLU(userId); @@ -2227,7 +2227,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean hasBaseUserRestriction(String restrictionKey, @UserIdInt int userId) { - checkManageOrCreateUsersPermission("hasBaseUserRestriction"); + checkCreateUsersPermission("hasBaseUserRestriction"); if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) { return false; } @@ -2444,7 +2444,7 @@ public class UserManagerService extends IUserManager.Stub { */ @Override public boolean canAddMoreUsersOfType(String userType) { - checkManageOrCreateUsersPermission("check if more users can be added."); + checkCreateUsersPermission("check if more users can be added."); final UserTypeDetails userTypeDetails = mUserTypes.get(userType); return userTypeDetails != null && canAddMoreUsersOfType(userTypeDetails); } @@ -2452,7 +2452,7 @@ public class UserManagerService extends IUserManager.Stub { /** Returns whether the creation of users of the given user type is enabled on this device. */ @Override public boolean isUserTypeEnabled(String userType) { - checkManageOrCreateUsersPermission("check if user type is enabled."); + checkCreateUsersPermission("check if user type is enabled."); final UserTypeDetails userTypeDetails = mUserTypes.get(userType); return userTypeDetails != null && userTypeDetails.isEnabled(); } @@ -2577,10 +2577,10 @@ public class UserManagerService extends IUserManager.Stub { * * @param message used as message if SecurityException is thrown * @throws SecurityException if the caller is not system or root - * @see #hasManageOrCreateUsersPermission() + * @see #hasCreateUsersPermission() */ - private static final void checkManageOrCreateUsersPermission(String message) { - if (!hasManageOrCreateUsersPermission()) { + private static final void checkCreateUsersPermission(String message) { + if (!hasCreateUsersPermission()) { throw new SecurityException( "You either need MANAGE_USERS or CREATE_USERS permission to: " + message); } @@ -2621,14 +2621,14 @@ public class UserManagerService extends IUserManager.Stub { } /** - * Similar to {@link #checkManageOrCreateUsersPermission(String)} but when the caller is tries + * Similar to {@link #checkCreateUsersPermission(String)} but when the caller is tries * to create user/profiles other than what is allowed for * {@link android.Manifest.permission#CREATE_USERS CREATE_USERS} permission, then it will only * allow callers with {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} permission. */ - private static final void checkManageOrCreateUsersPermission(int creationFlags) { + private static final void checkCreateUsersPermission(int creationFlags) { if ((creationFlags & ~ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION) == 0) { - if (!hasManageOrCreateUsersPermission()) { + if (!hasCreateUsersPermission()) { throw new SecurityException("You either need MANAGE_USERS or CREATE_USERS " + "permission to create an user with flags: " + creationFlags); } @@ -2672,7 +2672,7 @@ public class UserManagerService extends IUserManager.Stub { * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or * {@link android.Manifest.permission#CREATE_USERS CREATE_USERS}. */ - private static final boolean hasManageOrCreateUsersPermission() { + private static final boolean hasCreateUsersPermission() { return hasManageUsersOrPermission(android.Manifest.permission.CREATE_USERS); } @@ -2692,7 +2692,7 @@ public class UserManagerService extends IUserManager.Stub { * {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}. */ private static final boolean hasQueryOrCreateUsersPermission() { - return hasManageOrCreateUsersPermission() + return hasCreateUsersPermission() || hasPermissionGranted(Manifest.permission.QUERY_USERS, Binder.getCallingUid()); } @@ -3572,7 +3572,7 @@ public class UserManagerService extends IUserManager.Stub { public UserInfo createProfileForUserWithThrow(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) throws ServiceSpecificException { - checkManageOrCreateUsersPermission(flags); + checkCreateUsersPermission(flags); try { return createUserInternal(name, userType, flags, userId, disallowedPackages); } catch (UserManager.CheckedUserOperationException e) { @@ -3588,7 +3588,7 @@ public class UserManagerService extends IUserManager.Stub { @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) throws ServiceSpecificException { - checkManageOrCreateUsersPermission(flags); + checkCreateUsersPermission(flags); try { return createUserInternalUnchecked(name, userType, flags, userId, /* preCreate= */ false, disallowedPackages, /* token= */ null); @@ -3601,7 +3601,7 @@ public class UserManagerService extends IUserManager.Stub { public UserInfo createUserWithThrow(String name, @NonNull String userType, @UserInfoFlag int flags) throws ServiceSpecificException { - checkManageOrCreateUsersPermission(flags); + checkCreateUsersPermission(flags); try { return createUserInternal(name, userType, flags, UserHandle.USER_NULL, /* disallowedPackages= */ null); @@ -3615,7 +3615,7 @@ public class UserManagerService extends IUserManager.Stub { final UserTypeDetails userTypeDetails = mUserTypes.get(userType); final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0; - checkManageOrCreateUsersPermission(flags); + checkCreateUsersPermission(flags); Preconditions.checkArgument(isUserTypeEligibleForPreCreation(userTypeDetails), "cannot pre-create user of type " + userType); @@ -3635,7 +3635,7 @@ public class UserManagerService extends IUserManager.Stub { String userName, String userType, @UserInfoFlag int flags, Bitmap userIcon, String accountName, String accountType, PersistableBundle accountOptions) { - checkManageOrCreateUsersPermission(flags); + checkCreateUsersPermission(flags); if (someUserHasAccountNoChecks(accountName, accountType)) { throw new ServiceSpecificException( @@ -4080,7 +4080,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public String[] getPreInstallableSystemPackages(@NonNull String userType) { - checkManageOrCreateUsersPermission("getPreInstallableSystemPackages"); + checkCreateUsersPermission("getPreInstallableSystemPackages"); final Set<String> installableSystemPackages = mSystemPackageInstaller.getInstallablePackagesForUserType(userType); if (installableSystemPackages == null) { @@ -4205,7 +4205,7 @@ public class UserManagerService extends IUserManager.Stub { */ @Override public UserInfo createRestrictedProfileWithThrow(@Nullable String name, int parentUserId) { - checkManageOrCreateUsersPermission("setupRestrictedProfile"); + checkCreateUsersPermission("setupRestrictedProfile"); final UserInfo user = createProfileForUserWithThrow( name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null); if (user == null) { @@ -4302,7 +4302,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean removeUser(@UserIdInt int userId) { Slog.i(LOG_TAG, "removeUser u" + userId); - checkManageOrCreateUsersPermission("Only the system can remove users"); + checkCreateUsersPermission("Only the system can remove users"); final String restriction = getUserRemovalRestriction(userId); if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { @@ -4314,7 +4314,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { - checkManageOrCreateUsersPermission("Only the system can remove users"); + checkCreateUsersPermission("Only the system can remove users"); return removeUserUnchecked(userId); } @@ -4429,7 +4429,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId, boolean evenWhenDisallowed) { - checkManageOrCreateUsersPermission("Only the system can remove users"); + checkCreateUsersPermission("Only the system can remove users"); if (!evenWhenDisallowed) { final String restriction = getUserRemovalRestriction(userId); @@ -5180,7 +5180,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean someUserHasAccount(String accountName, String accountType) { - checkManageOrCreateUsersPermission("check seed account information"); + checkCreateUsersPermission("check seed account information"); return someUserHasAccountNoChecks(accountName, accountType); } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 111087f0baf2..a3b0e3e7d02d 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -1197,6 +1197,7 @@ public class DomainVerificationService extends SystemService @Nullable @UserIdInt Integer userId, @NonNull Function<String, PackageStateInternal> pkgSettingFunction) throws NameNotFoundException { + mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy); synchronized (mLock) { mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index eb6895264c60..44d36237b555 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -167,7 +167,6 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.ColorSpace; -import android.graphics.GraphicBuffer; import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; @@ -4092,8 +4091,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Make IME snapshot as trusted overlay InputMonitor.setTrustedOverlayInputInfo(imeSurface, t, getDisplayId(), "IME-snapshot-surface"); - GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(buffer); - t.setBuffer(imeSurface, graphicBuffer); + t.setBuffer(imeSurface, buffer); t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB)); t.setRelativeLayer(imeSurface, activity.getSurfaceControl(), 1); t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left, diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 94a175caba22..8a2d11636fe3 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -251,15 +251,47 @@ public class LockTaskController { */ boolean activityBlockedFromFinish(ActivityRecord activity) { final Task task = activity.getTask(); - if (activity == task.getRootActivity() - && activity == task.getTopNonFinishingActivity() - && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV - && isRootTask(task)) { - Slog.i(TAG, "Not finishing task in lock task mode"); - showLockTaskToast(); - return true; + if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV || !isRootTask(task)) { + return false; } - return false; + + final ActivityRecord taskTop = task.getTopNonFinishingActivity(); + final ActivityRecord taskRoot = task.getRootActivity(); + // If task has more than one Activity, verify if there's only adjacent TaskFragments that + // should be finish together in the Task. + if (activity != taskRoot || activity != taskTop) { + final TaskFragment taskFragment = activity.getTaskFragment(); + final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); + if (taskFragment.asTask() != null + || !taskFragment.isDelayLastActivityRemoval() + || adjacentTaskFragment == null) { + // Don't block activity from finishing if the TaskFragment don't have any adjacent + // TaskFragment, or it won't finish together with its adjacent TaskFragment. + return false; + } + + final boolean hasOtherActivityInTaskFragment = + taskFragment.getActivity(a -> !a.finishing && a != activity) != null; + if (hasOtherActivityInTaskFragment) { + // Don't block activity from finishing if there's other Activity in the same + // TaskFragment. + return false; + } + + final boolean hasOtherActivityInTask = task.getActivity(a -> !a.finishing + && a != activity && a.getTaskFragment() != adjacentTaskFragment) != null; + if (hasOtherActivityInTask) { + // Do not block activity from finishing if there are another running activities + // after the current and adjacent TaskFragments are removed. Note that we don't + // check activities in adjacent TaskFragment because it will be finished together + // with TaskFragment regardless of numbers of activities. + return false; + } + } + + Slog.i(TAG, "Not finishing task in lock task mode"); + showLockTaskToast(); + return true; } /** diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index c845dca57c18..97cb512455f6 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -240,7 +240,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** * Whether to delay the last activity of TaskFragment being immediately removed while finishing. * This should only be set on a embedded TaskFragment, where the organizer can have the - * opportunity to perform other actions or animations. + * opportunity to perform animations and finishing the adjacent TaskFragment. */ private boolean mDelayLastActivityRemoval; diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index f72f2cc156ec..f5eb0a7a34d3 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -35,6 +35,7 @@ cc_library_static { "com_android_server_am_BatteryStatsService.cpp", "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp", "com_android_server_ConsumerIrService.cpp", + "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp new file mode 100644 index 000000000000..43018a900f4c --- /dev/null +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "InputController" + +#include <android-base/unique_fd.h> +#include <android/input.h> +#include <android/keycodes.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/uinput.h> +#include <math.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedUtfChars.h> +#include <utils/Log.h> + +#include <map> +#include <string> + +namespace android { + +enum class DeviceType { + KEYBOARD, + MOUSE, + TOUCHSCREEN, +}; + +enum class UinputAction { + RELEASE = 0, + PRESS = 1, + MOVE = 2, + CANCEL = 3, +}; + +static std::map<int, UinputAction> BUTTON_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE}, +}; + +static std::map<int, UinputAction> KEY_ACTION_MAPPING = { + {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE}, +}; + +static std::map<int, UinputAction> TOUCH_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE}, + {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE}, + {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL}, +}; + +// Button code mapping from https://source.android.com/devices/input/touch-devices +static std::map<int, int> BUTTON_CODE_MAPPING = { + {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT}, {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT}, + {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE}, {AMOTION_EVENT_BUTTON_BACK, BTN_BACK}, + {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD}, +}; + +// Tool type mapping from https://source.android.com/devices/input/touch-devices +static std::map<int, int> TOOL_TYPE_MAPPING = { + {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER}, + {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM}, +}; + +// Keycode mapping from https://source.android.com/devices/input/keyboard-devices +static std::map<int, int> KEY_CODE_MAPPING = { + {AKEYCODE_0, KEY_0}, + {AKEYCODE_1, KEY_1}, + {AKEYCODE_2, KEY_2}, + {AKEYCODE_3, KEY_3}, + {AKEYCODE_4, KEY_4}, + {AKEYCODE_5, KEY_5}, + {AKEYCODE_6, KEY_6}, + {AKEYCODE_7, KEY_7}, + {AKEYCODE_8, KEY_8}, + {AKEYCODE_9, KEY_9}, + {AKEYCODE_A, KEY_A}, + {AKEYCODE_B, KEY_B}, + {AKEYCODE_C, KEY_C}, + {AKEYCODE_D, KEY_D}, + {AKEYCODE_E, KEY_E}, + {AKEYCODE_F, KEY_F}, + {AKEYCODE_G, KEY_G}, + {AKEYCODE_H, KEY_H}, + {AKEYCODE_I, KEY_I}, + {AKEYCODE_J, KEY_J}, + {AKEYCODE_K, KEY_K}, + {AKEYCODE_L, KEY_L}, + {AKEYCODE_M, KEY_M}, + {AKEYCODE_N, KEY_N}, + {AKEYCODE_O, KEY_O}, + {AKEYCODE_P, KEY_P}, + {AKEYCODE_Q, KEY_Q}, + {AKEYCODE_R, KEY_R}, + {AKEYCODE_S, KEY_S}, + {AKEYCODE_T, KEY_T}, + {AKEYCODE_U, KEY_U}, + {AKEYCODE_V, KEY_V}, + {AKEYCODE_W, KEY_W}, + {AKEYCODE_X, KEY_X}, + {AKEYCODE_Y, KEY_Y}, + {AKEYCODE_Z, KEY_Z}, + {AKEYCODE_GRAVE, KEY_GRAVE}, + {AKEYCODE_MINUS, KEY_MINUS}, + {AKEYCODE_EQUALS, KEY_EQUAL}, + {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE}, + {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE}, + {AKEYCODE_BACKSLASH, KEY_BACKSLASH}, + {AKEYCODE_SEMICOLON, KEY_SEMICOLON}, + {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE}, + {AKEYCODE_COMMA, KEY_COMMA}, + {AKEYCODE_PERIOD, KEY_DOT}, + {AKEYCODE_SLASH, KEY_SLASH}, + {AKEYCODE_ALT_LEFT, KEY_LEFTALT}, + {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT}, + {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL}, + {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL}, + {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT}, + {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT}, + {AKEYCODE_META_LEFT, KEY_LEFTMETA}, + {AKEYCODE_META_RIGHT, KEY_RIGHTMETA}, + {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK}, + {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK}, + {AKEYCODE_NUM_LOCK, KEY_NUMLOCK}, + {AKEYCODE_ENTER, KEY_ENTER}, + {AKEYCODE_TAB, KEY_TAB}, + {AKEYCODE_SPACE, KEY_SPACE}, + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, + {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, + {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_MOVE_END, KEY_END}, + {AKEYCODE_MOVE_HOME, KEY_HOME}, + {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN}, + {AKEYCODE_PAGE_UP, KEY_PAGEUP}, + {AKEYCODE_DEL, KEY_BACKSPACE}, + {AKEYCODE_FORWARD_DEL, KEY_DELETE}, + {AKEYCODE_INSERT, KEY_INSERT}, + {AKEYCODE_ESCAPE, KEY_ESC}, + {AKEYCODE_BREAK, KEY_PAUSE}, + {AKEYCODE_F1, KEY_F1}, + {AKEYCODE_F2, KEY_F2}, + {AKEYCODE_F3, KEY_F3}, + {AKEYCODE_F4, KEY_F4}, + {AKEYCODE_F5, KEY_F5}, + {AKEYCODE_F6, KEY_F6}, + {AKEYCODE_F7, KEY_F7}, + {AKEYCODE_F8, KEY_F8}, + {AKEYCODE_F9, KEY_F9}, + {AKEYCODE_F10, KEY_F10}, + {AKEYCODE_F11, KEY_F11}, + {AKEYCODE_F12, KEY_F12}, + {AKEYCODE_BACK, KEY_BACK}, + {AKEYCODE_FORWARD, KEY_FORWARD}, + {AKEYCODE_NUMPAD_1, KEY_KP1}, + {AKEYCODE_NUMPAD_2, KEY_KP2}, + {AKEYCODE_NUMPAD_3, KEY_KP3}, + {AKEYCODE_NUMPAD_4, KEY_KP4}, + {AKEYCODE_NUMPAD_5, KEY_KP5}, + {AKEYCODE_NUMPAD_6, KEY_KP6}, + {AKEYCODE_NUMPAD_7, KEY_KP7}, + {AKEYCODE_NUMPAD_8, KEY_KP8}, + {AKEYCODE_NUMPAD_9, KEY_KP9}, + {AKEYCODE_NUMPAD_0, KEY_KP0}, + {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS}, + {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS}, + {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK}, + {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH}, + {AKEYCODE_NUMPAD_DOT, KEY_KPDOT}, + {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER}, + {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL}, + {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA}, +}; + +/** Creates a new uinput device and assigns a file descriptor. */ +static int openUinput(const char* readableName, jint vendorId, jint productId, + DeviceType deviceType, jint screenHeight, jint screenWidth) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK))); + if (fd < 0) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return -errno; + } + + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_SYN); + switch (deviceType) { + case DeviceType::KEYBOARD: + for (const auto& [ignored, keyCode] : KEY_CODE_MAPPING) { + ioctl(fd, UI_SET_KEYBIT, keyCode); + } + break; + case DeviceType::MOUSE: + ioctl(fd, UI_SET_EVBIT, EV_REL); + ioctl(fd, UI_SET_KEYBIT, BTN_LEFT); + ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT); + ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE); + ioctl(fd, UI_SET_KEYBIT, BTN_BACK); + ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD); + ioctl(fd, UI_SET_RELBIT, REL_X); + ioctl(fd, UI_SET_RELBIT, REL_Y); + ioctl(fd, UI_SET_RELBIT, REL_WHEEL); + ioctl(fd, UI_SET_RELBIT, REL_HWHEEL); + break; + case DeviceType::TOUCHSCREEN: + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE); + ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); + } + + int version; + if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) { + uinput_setup setup; + memset(&setup, 0, sizeof(setup)); + strlcpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE); + setup.id.version = 1; + setup.id.bustype = BUS_VIRTUAL; + setup.id.vendor = vendorId; + setup.id.product = productId; + if (deviceType == DeviceType::TOUCHSCREEN) { + uinput_abs_setup xAbsSetup; + xAbsSetup.code = ABS_MT_POSITION_X; + xAbsSetup.absinfo.maximum = screenWidth - 1; + xAbsSetup.absinfo.minimum = 0; + ioctl(fd, UI_ABS_SETUP, xAbsSetup); + uinput_abs_setup yAbsSetup; + yAbsSetup.code = ABS_MT_POSITION_Y; + yAbsSetup.absinfo.maximum = screenHeight - 1; + yAbsSetup.absinfo.minimum = 0; + ioctl(fd, UI_ABS_SETUP, yAbsSetup); + uinput_abs_setup majorAbsSetup; + majorAbsSetup.code = ABS_MT_TOUCH_MAJOR; + majorAbsSetup.absinfo.maximum = screenWidth - 1; + majorAbsSetup.absinfo.minimum = 0; + ioctl(fd, UI_ABS_SETUP, majorAbsSetup); + uinput_abs_setup pressureAbsSetup; + pressureAbsSetup.code = ABS_MT_PRESSURE; + pressureAbsSetup.absinfo.maximum = 255; + pressureAbsSetup.absinfo.minimum = 0; + ioctl(fd, UI_ABS_SETUP, pressureAbsSetup); + } + if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return -errno; + } + } else { + // UI_DEV_SETUP was not introduced until version 5. Try setting up manually. + uinput_user_dev fallback; + memset(&fallback, 0, sizeof(fallback)); + strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE); + fallback.id.version = 1; + fallback.id.bustype = BUS_VIRTUAL; + fallback.id.vendor = vendorId; + fallback.id.product = productId; + if (deviceType == DeviceType::TOUCHSCREEN) { + fallback.absmin[ABS_MT_POSITION_X] = 0; + fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1; + fallback.absmin[ABS_MT_POSITION_Y] = 0; + fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1; + fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0; + fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1; + fallback.absmin[ABS_MT_PRESSURE] = 0; + fallback.absmax[ABS_MT_PRESSURE] = 255; + } + if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return -errno; + } + } + + if (ioctl(fd, UI_DEV_CREATE) != 0) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return -errno; + } + + return fd.release(); +} + +static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, + DeviceType deviceType, int screenHeight, int screenWidth) { + ScopedUtfChars readableName(env, name); + return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight, + screenWidth); +} + +static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId, + jint productId) { + return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0, + /* screenWidth */ 0); +} + +static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId, + jint productId) { + return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0, + /* screenWidth */ 0); +} + +static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId, + jint productId, jint height, jint width) { + return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width); +} + +static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) { + ioctl(fd, UI_DEV_DESTROY); + return close(fd); +} + +static bool writeInputEvent(int fd, uint16_t type, uint16_t code, int32_t value) { + struct input_event ev = {.type = type, .code = code, .value = value}; + return TEMP_FAILURE_RETRY(write(fd, &ev, sizeof(struct input_event))) == sizeof(ev); +} + +static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode, + jint action) { + auto keyCodeIterator = KEY_CODE_MAPPING.find(androidKeyCode); + if (keyCodeIterator == KEY_CODE_MAPPING.end()) { + ALOGE("No supportive native keycode for androidKeyCode %d", androidKeyCode); + return false; + } + auto actionIterator = KEY_ACTION_MAPPING.find(action); + if (actionIterator == KEY_ACTION_MAPPING.end()) { + return false; + } + if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(keyCodeIterator->second), + static_cast<int32_t>(actionIterator->second))) { + return false; + } + if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) { + return false; + } + return true; +} + +static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jint fd, jint buttonCode, + jint action) { + auto buttonCodeIterator = BUTTON_CODE_MAPPING.find(buttonCode); + if (buttonCodeIterator == BUTTON_CODE_MAPPING.end()) { + return false; + } + auto actionIterator = BUTTON_ACTION_MAPPING.find(action); + if (actionIterator == BUTTON_ACTION_MAPPING.end()) { + return false; + } + if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(buttonCodeIterator->second), + static_cast<int32_t>(actionIterator->second))) { + return false; + } + if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) { + return false; + } + return true; +} + +static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jint fd, jint pointerId, jint toolType, + jint action, jfloat locationX, jfloat locationY, jfloat pressure, + jfloat majorAxisSize) { + if (!writeInputEvent(fd, EV_ABS, ABS_MT_SLOT, pointerId)) { + return false; + } + auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType); + if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) { + return false; + } + if (toolType != -1) { + if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOOL_TYPE, + static_cast<int32_t>(toolTypeIterator->second))) { + return false; + } + } + auto actionIterator = TOUCH_ACTION_MAPPING.find(action); + if (actionIterator == TOUCH_ACTION_MAPPING.end()) { + return false; + } + UinputAction uinputAction = actionIterator->second; + if (uinputAction == UinputAction::PRESS || uinputAction == UinputAction::RELEASE) { + if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(uinputAction))) { + return false; + } + if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, + static_cast<int32_t>(uinputAction == UinputAction::PRESS ? pointerId + : -1))) { + return false; + } + } + if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_X, locationX)) { + return false; + } + if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_Y, locationY)) { + return false; + } + if (!isnan(pressure)) { + if (!writeInputEvent(fd, EV_ABS, ABS_MT_PRESSURE, pressure)) { + return false; + } + } + if (!isnan(majorAxisSize)) { + if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) { + return false; + } + } + return writeInputEvent(fd, EV_SYN, SYN_REPORT, 0); +} + +static bool nativeWriteRelativeEvent(JNIEnv* env, jobject thiz, jint fd, jfloat relativeX, + jfloat relativeY) { + return writeInputEvent(fd, EV_REL, REL_X, relativeX) && + writeInputEvent(fd, EV_REL, REL_Y, relativeY) && + writeInputEvent(fd, EV_SYN, SYN_REPORT, 0); +} + +static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jint fd, jfloat xAxisMovement, + jfloat yAxisMovement) { + return writeInputEvent(fd, EV_REL, REL_HWHEEL, xAxisMovement) && + writeInputEvent(fd, EV_REL, REL_WHEEL, yAxisMovement) && + writeInputEvent(fd, EV_SYN, SYN_REPORT, 0); +} + +static JNINativeMethod methods[] = { + {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard}, + {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse}, + {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I", + (void*)nativeOpenUinputTouchscreen}, + {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput}, + {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent}, + {"nativeWriteButtonEvent", "(III)Z", (void*)nativeWriteButtonEvent}, + {"nativeWriteTouchEvent", "(IIIIFFFF)Z", (void*)nativeWriteTouchEvent}, + {"nativeWriteRelativeEvent", "(IFF)Z", (void*)nativeWriteRelativeEvent}, + {"nativeWriteScrollEvent", "(IFF)Z", (void*)nativeWriteScrollEvent}, +}; + +int register_android_server_companion_virtual_InputController(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/InputController", + methods, NELEM(methods)); +} + +} // namespace android
\ No newline at end of file diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index ff61abc4ff7f..d339ef1154c5 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -63,6 +63,7 @@ int register_android_server_FaceService(JNIEnv* env); int register_android_server_GpuService(JNIEnv* env); int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); +int register_android_server_companion_virtual_InputController(JNIEnv* env); }; using namespace android; @@ -119,5 +120,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_GpuService(env); register_android_server_stats_pull_StatsPullAtomService(env); register_android_server_sensor_SensorService(vm, env); + register_android_server_companion_virtual_InputController(env); return JNI_VERSION_1_4; } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index d1d7cc6422c3..d9dbf48f969f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.pm.SigningDetails import android.content.pm.parsing.component.ParsedActivityImpl import android.content.pm.parsing.component.ParsedIntentInfoImpl import android.content.pm.verify.domain.DomainVerificationManager @@ -26,6 +27,7 @@ import android.content.pm.verify.domain.DomainVerificationState import android.os.Build import android.os.Process import android.util.ArraySet +import android.util.IndentingPrintWriter import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry import com.android.server.pm.parsing.pkg.AndroidPackage @@ -46,6 +48,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyString import org.mockito.Mockito.eq +import org.mockito.Mockito.mock import org.mockito.Mockito.verifyNoMoreInteractions import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean @@ -204,6 +207,14 @@ class DomainVerificationEnforcerTest { service(Type.QUERENT, "getInfo") { getDomainVerificationInfo(it.targetPackageName) }, + service(Type.QUERENT, "printState") { + printState(mock(IndentingPrintWriter::class.java), null, null) + }, + service(Type.QUERENT, "printStateInternal") { + printState(mock(IndentingPrintWriter::class.java), null, null) { + mockPkgState(it, UUID.randomUUID()) + } + }, service(Type.VERIFIER, "setStatus") { setDomainVerificationStatus( it.targetDomainSetId, @@ -311,6 +322,7 @@ class DomainVerificationEnforcerTest { } ) } + whenever(signingDetails) { SigningDetails.UNKNOWN } } fun mockPkgState(packageName: String, domainSetId: UUID) = @@ -327,6 +339,7 @@ class DomainVerificationEnforcerTest { } } whenever(isSystem) { false } + whenever(signingDetails) { SigningDetails.UNKNOWN } } } @@ -794,8 +807,12 @@ class DomainVerificationEnforcerTest { } val valueAsInt = value as? Int - if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) { - throw AssertionError("Expected call to return false, was $value") + if (valueAsInt != null) { + if (valueAsInt == DomainVerificationManager.STATUS_OK) { + throw AssertionError("Expected call to return false, was $value") + } + } else { + throw AssertionError("Expected call to fail") } } catch (e: SecurityException) { } catch (e: PackageManager.NameNotFoundException) { diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index e3c60fdfc697..c3a364e723fb 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -32,6 +32,7 @@ android_test { "services.appwidget", "services.autofill", "services.backup", + "services.companion", "services.core", "services.devicepolicy", "services.net", diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index f92b872e1d26..28c1c815508f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -585,8 +585,8 @@ public class AbstractAccessibilityServiceConnectionTest { doAnswer((invocation) -> { ((Region) invocation.getArguments()[1]).set(region); return null; - }).when(mMockMagnificationProcessor).getMagnificationRegion(eq(displayId), - any(), anyBoolean()); + }).when(mMockMagnificationProcessor).getFullscreenMagnificationRegion(eq(displayId), any(), + anyBoolean()); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final Region result = mServiceConnection.getMagnificationRegion(displayId); @@ -620,7 +620,8 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void resetMagnification() { final int displayId = 1; - when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true); + when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn( + true); final boolean result = mServiceConnection.resetMagnification(displayId, true); assertThat(result, is(true)); @@ -629,7 +630,8 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void resetMagnification_cantControlMagnification_returnFalse() { final int displayId = 1; - when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true); + when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn( + true); when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false); final boolean result = mServiceConnection.resetMagnification(displayId, true); @@ -639,7 +641,8 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void resetMagnification_serviceNotBelongCurrentUser_returnFalse() { final int displayId = 1; - when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true); + when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn( + true); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final boolean result = mServiceConnection.resetMagnification(displayId, true); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java index 9ebec981fa21..621507e2bfc8 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java @@ -98,7 +98,7 @@ public class MagnificationProcessorTest { .setScale(TEST_SCALE).build(); setMagnificationActivated(TEST_DISPLAY, config); - float scale = mMagnificationProcessor.getScale(TEST_DISPLAY); + float scale = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getScale(); assertEquals(scale, TEST_SCALE, 0); } @@ -111,20 +111,19 @@ public class MagnificationProcessorTest { setMagnificationActivated(TEST_DISPLAY, config); float centerX = mMagnificationProcessor.getCenterX( - TEST_DISPLAY, /* canControlMagnification= */true); + TEST_DISPLAY, /* canControlMagnification= */true); assertEquals(centerX, TEST_CENTER_X, 0); } @Test - public void getCenterX_canControlWindowMagnification_returnCenterX() { + public void getCenterX_controlWindowMagnification_returnCenterX() { final MagnificationConfig config = new MagnificationConfig.Builder() .setMode(MAGNIFICATION_MODE_WINDOW) .setCenterX(TEST_CENTER_X).build(); setMagnificationActivated(TEST_DISPLAY, config); - float centerX = mMagnificationProcessor.getCenterX( - TEST_DISPLAY, /* canControlMagnification= */true); + float centerX = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterX(); assertEquals(centerX, TEST_CENTER_X, 0); } @@ -143,14 +142,13 @@ public class MagnificationProcessorTest { } @Test - public void getCenterY_canControlWindowMagnification_returnCenterY() { + public void getCenterY_controlWindowMagnification_returnCenterY() { final MagnificationConfig config = new MagnificationConfig.Builder() .setMode(MAGNIFICATION_MODE_WINDOW) .setCenterY(TEST_CENTER_Y).build(); setMagnificationActivated(TEST_DISPLAY, config); - float centerY = mMagnificationProcessor.getCenterY( - TEST_DISPLAY, /* canControlMagnification= */false); + float centerY = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterY(); assertEquals(centerY, TEST_CENTER_Y, 0); } @@ -159,7 +157,7 @@ public class MagnificationProcessorTest { public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() { final Region region = new Region(10, 20, 100, 200); setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN); - mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, + mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY, region, /* canControlMagnification= */true); verify(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY), @@ -167,17 +165,6 @@ public class MagnificationProcessorTest { } @Test - public void getMagnificationRegion_canControlWindowMagnification_returnRegion() { - final Region region = new Region(10, 20, 100, 200); - setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW); - mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, - region, /* canControlMagnification= */true); - - verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY), - eq(region)); - } - - @Test public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { final Region region = new Region(10, 20, 100, 200); setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN); @@ -188,7 +175,7 @@ public class MagnificationProcessorTest { any()); final Region result = new Region(); - mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, + mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY, result, /* canControlMagnification= */true); assertEquals(region, result); verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY); @@ -237,7 +224,7 @@ public class MagnificationProcessorTest { public void reset_fullscreenMagnificationActivated() { setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN); - mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false); + mMagnificationProcessor.resetFullscreenMagnification(TEST_DISPLAY, /* animate= */false); verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false); } @@ -246,7 +233,7 @@ public class MagnificationProcessorTest { public void reset_windowMagnificationActivated() { setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW); - mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false); + mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false); verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java new file mode 100644 index 000000000000..c7c0756bc0d0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertThrows; + +import android.Manifest; +import android.content.Context; +import android.content.ContextWrapper; +import android.graphics.Point; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.VirtualKeyEvent; +import android.hardware.input.VirtualMouseButtonEvent; +import android.hardware.input.VirtualMouseRelativeEvent; +import android.hardware.input.VirtualMouseScrollEvent; +import android.hardware.input.VirtualTouchEvent; +import android.os.Binder; +import android.platform.test.annotations.Presubmit; +import android.view.KeyEvent; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class VirtualDeviceManagerServiceTest { + + private static final String DEVICE_NAME = "device name"; + private static final int DISPLAY_ID = 2; + private static final int PRODUCT_ID = 10; + private static final int VENDOR_ID = 5; + private static final int HEIGHT = 1800; + private static final int WIDTH = 900; + private static final Binder BINDER = new Binder("binder"); + + private Context mContext; + private VirtualDeviceImpl mDeviceImpl; + private InputController mInputController; + @Mock + private InputController.NativeWrapper mNativeWrapperMock; + @Mock + private DisplayManagerInternal mDisplayManagerInternalMock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + doNothing().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + mInputController = new InputController(new Object(), mNativeWrapperMock); + mDeviceImpl = new VirtualDeviceImpl(mContext, + /* association info */ null, new Binder(), /* uid */ 0, mInputController, + (int associationId) -> {}); + } + + @Test + public void createVirtualKeyboard_noDisplay_failsSecurityException() { + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, + PRODUCT_ID, BINDER)); + } + + @Test + public void createVirtualMouse_noDisplay_failsSecurityException() { + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, + PRODUCT_ID, BINDER)); + } + + @Test + public void createVirtualTouchscreen_noDisplay_failsSecurityException() { + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, + VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT))); + } + + @Test + public void createVirtualKeyboard_noPermission_failsSecurityException() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, + PRODUCT_ID, BINDER)); + } + + @Test + public void createVirtualMouse_noPermission_failsSecurityException() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, + PRODUCT_ID, BINDER)); + } + + @Test + public void createVirtualTouchscreen_noPermission_failsSecurityException() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, + VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT))); + } + + @Test + public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, + BINDER); + assertWithMessage("Virtual keyboard should register fd when the display matches") + .that(mInputController.mInputDeviceFds).isNotEmpty(); + verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); + } + + @Test + public void createVirtualMouse_hasDisplay_obtainFileDescriptor() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, + BINDER); + assertWithMessage("Virtual keyboard should register fd when the display matches") + .that(mInputController.mInputDeviceFds).isNotEmpty(); + verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); + } + + @Test + public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, + BINDER, new Point(WIDTH, HEIGHT)); + assertWithMessage("Virtual keyboard should register fd when the display matches") + .that(mInputController.mInputDeviceFds).isNotEmpty(); + verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT, + WIDTH); + } + + @Test + public void sendKeyEvent_noFd() { + assertThrows( + IllegalArgumentException.class, + () -> + mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder() + .setKeyCode(KeyEvent.KEYCODE_A) + .setAction(VirtualKeyEvent.ACTION_DOWN).build())); + } + + @Test + public void sendKeyEvent_hasFd_writesEvent() { + final int fd = 1; + final int keyCode = KeyEvent.KEYCODE_A; + final int action = VirtualKeyEvent.ACTION_UP; + mInputController.mInputDeviceFds.put(BINDER, fd); + mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode) + .setAction(action).build()); + verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); + } + + @Test + public void sendButtonEvent_noFd() { + assertThrows( + IllegalArgumentException.class, + () -> + mDeviceImpl.sendButtonEvent(BINDER, + new VirtualMouseButtonEvent.Builder() + .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK) + .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS) + .build())); + } + + @Test + public void sendButtonEvent_hasFd_writesEvent() { + final int fd = 1; + final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; + final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; + mInputController.mInputDeviceFds.put(BINDER, fd); + mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() + .setButtonCode(buttonCode) + .setAction(action).build()); + verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action); + } + + @Test + public void sendRelativeEvent_noFd() { + assertThrows( + IllegalArgumentException.class, + () -> + mDeviceImpl.sendRelativeEvent(BINDER, + new VirtualMouseRelativeEvent.Builder().setRelativeX( + 0.0f).setRelativeY(0.0f).build())); + } + + @Test + public void sendRelativeEvent_hasFd_writesEvent() { + final int fd = 1; + final float x = -0.2f; + final float y = 0.7f; + mInputController.mInputDeviceFds.put(BINDER, fd); + mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() + .setRelativeX(x).setRelativeY(y).build()); + verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y); + } + + @Test + public void sendScrollEvent_noFd() { + assertThrows( + IllegalArgumentException.class, + () -> + mDeviceImpl.sendScrollEvent(BINDER, + new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(-1f) + .setYAxisMovement(1f).build())); + } + + @Test + public void sendScrollEvent_hasFd_writesEvent() { + final int fd = 1; + final float x = 0.5f; + final float y = 1f; + mInputController.mInputDeviceFds.put(BINDER, fd); + mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(x) + .setYAxisMovement(y).build()); + verify(mNativeWrapperMock).writeScrollEvent(fd, x, y); + } + + @Test + public void sendTouchEvent_noFd() { + assertThrows( + IllegalArgumentException.class, + () -> + mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder() + .setX(0.0f) + .setY(0.0f) + .setAction(VirtualTouchEvent.ACTION_UP) + .setPointerId(1) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .build())); + } + + @Test + public void sendTouchEvent_hasFd_writesEvent_withoutPressureOrMajorAxisSize() { + final int fd = 1; + final int pointerId = 5; + final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER; + final float x = 100.5f; + final float y = 200.5f; + final int action = VirtualTouchEvent.ACTION_UP; + mInputController.mInputDeviceFds.put(BINDER, fd); + mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) + .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build()); + verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, + Float.NaN); + } + + @Test + public void sendTouchEvent_hasFd_writesEvent() { + final int fd = 1; + final int pointerId = 5; + final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER; + final float x = 100.5f; + final float y = 200.5f; + final int action = VirtualTouchEvent.ACTION_UP; + final float pressure = 1.0f; + final float majorAxisSize = 10.0f; + mInputController.mInputDeviceFds.put(BINDER, fd); + mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) + .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType) + .setPressure(pressure).setMajorAxisSize(majorAxisSize).build()); + verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure, + majorAxisSize); + } +} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 1daa4cfe49bf..c80d35b9e772 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5981,6 +5981,7 @@ public class CarrierConfigManager { sDefaults.putInt( KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG, 120000); + sDefaults.putAll(ImsServiceEntitlement.getDefaults()); sDefaults.putAll(Gps.getDefaults()); sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY, new int[] { diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java index 793c37745de6..c25ace0c6a62 100644 --- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java +++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java @@ -50,6 +50,7 @@ public final class RcsClientConfiguration implements Parcelable { private String mRcsProfile; private String mClientVendor; private String mClientVersion; + private boolean mRcsEnabledByUser; /** * Create a RcsClientConfiguration object. @@ -63,14 +64,41 @@ public final class RcsClientConfiguration implements Parcelable { * @param clientVersion Identifies the RCS client version. Refer to GSMA * RCC.07 "client_version" parameter. * Example:client_version=RCSAndrd-1.0 + * @deprecated Use {@link #RcsClientConfiguration(String, String, String, String, boolean)} + * instead. Deprecated prototype assumes that the user setting controlling RCS is enabled. */ + @Deprecated public RcsClientConfiguration(@NonNull String rcsVersion, @NonNull @StringRcsProfile String rcsProfile, @NonNull String clientVendor, @NonNull String clientVersion) { + this(rcsVersion, rcsProfile, clientVendor, clientVersion, true); + } + + /** + * Create a RcsClientConfiguration object. + * Default messaging application must pass a valid configuration object + * @param rcsVersion The parameter identifies the RCS version supported + * by the client. Refer to GSMA RCC.07 "rcs_version" parameter. + * @param rcsProfile Identifies a fixed set of RCS services that are + * supported by the client. See {@link #RCS_PROFILE_1_0 } or + * {@link #RCS_PROFILE_2_3 } + * @param clientVendor Identifies the vendor providing the RCS client. + * @param clientVersion Identifies the RCS client version. Refer to GSMA + * RCC.07 "client_version" parameter. + * Example:client_version=RCSAndrd-1.0 + * @param isRcsEnabledByUser The current user setting for whether or not the user has + * enabled or disabled RCS. Please refer to GSMA RCC.07 "rcs_state" parameter for how this + * can affect provisioning. + */ + public RcsClientConfiguration(@NonNull String rcsVersion, + @NonNull @StringRcsProfile String rcsProfile, + @NonNull String clientVendor, @NonNull String clientVersion, + boolean isRcsEnabledByUser) { mRcsVersion = rcsVersion; mRcsProfile = rcsProfile; mClientVendor = clientVendor; mClientVersion = clientVersion; + mRcsEnabledByUser = isRcsEnabledByUser; } /** @@ -102,6 +130,18 @@ public final class RcsClientConfiguration implements Parcelable { } /** + * The current user setting provided by the RCS messaging application that determines + * whether or not the user has enabled RCS. + * <p> + * See GSMA RCC.07 "rcs_state" parameter for more information about how this setting + * affects provisioning. + * @return true if RCS is enabled by the user, false if RCS is disabled by the user. + */ + public boolean isRcsEnabledByUser() { + return mRcsEnabledByUser; + } + + /** * {@link Parcelable#writeToParcel} */ @Override @@ -110,6 +150,7 @@ public final class RcsClientConfiguration implements Parcelable { out.writeString(mRcsProfile); out.writeString(mClientVendor); out.writeString(mClientVersion); + out.writeBoolean(mRcsEnabledByUser); } /** @@ -124,8 +165,9 @@ public final class RcsClientConfiguration implements Parcelable { String rcsProfile = in.readString(); String clientVendor = in.readString(); String clientVersion = in.readString(); + Boolean rcsEnabledByUser = in.readBoolean(); return new RcsClientConfiguration(rcsVersion, rcsProfile, - clientVendor, clientVersion); + clientVendor, clientVersion, rcsEnabledByUser); } @Override @@ -152,11 +194,13 @@ public final class RcsClientConfiguration implements Parcelable { return mRcsVersion.equals(other.mRcsVersion) && mRcsProfile.equals(other.mRcsProfile) && mClientVendor.equals(other.mClientVendor) - && mClientVersion.equals(other.mClientVersion); + && mClientVersion.equals(other.mClientVersion) + && (mRcsEnabledByUser == other.mRcsEnabledByUser); } @Override public int hashCode() { - return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion); + return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion, + mRcsEnabledByUser); } } diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 7a1c09275a6d..61de3ac2b25e 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -28,13 +28,10 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.aidl.IRcsUceControllerCallback; import android.telephony.ims.aidl.IRcsUcePublishStateCallback; -import android.telephony.ims.feature.RcsFeature; import android.util.Log; import java.lang.annotation.Retention; @@ -337,6 +334,14 @@ public class RcsUceAdapter { @SystemApi public static final int PUBLISH_STATE_OTHER_ERROR = 6; + /** + * The device is currently trying to publish its capabilities to the network. + * @hide + */ + @SystemApi + public static final int PUBLISH_STATE_PUBLISHING = 7; + + /**@hide*/ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "PUBLISH_STATE_", value = { @@ -345,7 +350,8 @@ public class RcsUceAdapter { PUBLISH_STATE_VOICE_PROVISION_ERROR, PUBLISH_STATE_RCS_PROVISION_ERROR, PUBLISH_STATE_REQUEST_TIMEOUT, - PUBLISH_STATE_OTHER_ERROR + PUBLISH_STATE_OTHER_ERROR, + PUBLISH_STATE_PUBLISHING }) public @interface PublishState {} @@ -480,9 +486,12 @@ public class RcsUceAdapter { * <p> * Be sure to check the availability of this feature using * {@link ImsRcsManager#isAvailable(int, int)} and ensuring - * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or - * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else - * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}. + * {@link + * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or + * {@link + * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is + * enabled or else this operation will fail with {@link #ERROR_NOT_AVAILABLE} or + * {@link #ERROR_NOT_ENABLED}. * * @param contactNumbers A list of numbers that the capabilities are being requested for. * @param executor The executor that will be used when the request is completed and the @@ -573,8 +582,10 @@ public class RcsUceAdapter { * <p> * Be sure to check the availability of this feature using * {@link ImsRcsManager#isAvailable(int, int)} and ensuring - * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or - * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is + * {@link + * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or + * {@link + * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is * enabled or else this operation will fail with * {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}. * @@ -690,7 +701,8 @@ public class RcsUceAdapter { * Registers a {@link OnPublishStateChangedListener} with the system, which will provide publish * state updates for the subscription specified in {@link ImsManager@getRcsManager(subid)}. * <p> - * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to subscription + * Use {@link android.telephony.SubscriptionManager.OnSubscriptionsChangedListener} to listen + * to subscription * changed events and call * {@link #removeOnPublishStateChangedListener(OnPublishStateChangedListener)} to clean up. * <p> @@ -792,7 +804,8 @@ public class RcsUceAdapter { * cache associated with those contacts as the local cache becomes stale. * <p> * This setting will only enable this feature if - * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled. + * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is + * also enabled. * <p> * Note: This setting does not affect whether or not the device publishes its service * capabilities if the subscription supports presence publication. @@ -843,7 +856,8 @@ public class RcsUceAdapter { * session. * <p> * This setting will only enable this feature if - * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled. + * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is + * also enabled. * <p> * Note: This setting does not affect whether or not the device publishes its service * capabilities if the subscription supports presence publication. diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java index c3d7325f2e0a..c27fa4fc882d 100644 --- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java +++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java @@ -81,6 +81,26 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis } /** + * Receives the status of changes in the publishing connection from ims service + * and deliver this callback to the framework. + */ + public void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase, + int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException { + ICapabilityExchangeEventListener listener = mListenerBinder; + if (listener == null) { + return; + } + try { + listener.onPublishUpdated(reasonCode, reasonPhrase, + reasonHeaderCause, reasonHeaderText); + } catch (RemoteException e) { + Log.w(LOG_TAG, "onPublishUpdated exception: " + e); + throw new ImsException("Remote is not available", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** * Receives the callback of the remote capability request from the network and deliver this * request to the framework. */ diff --git a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl index 078ac919b75e..c675bc3204f6 100644 --- a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl @@ -31,6 +31,8 @@ import java.util.List; oneway interface ICapabilityExchangeEventListener { void onRequestPublishCapabilities(int publishTriggerType); void onUnpublish(); + void onPublishUpdated(int reasonCode, String reasonPhrase, int reasonHeaderCause, + String reasonHeaderText); void onRemoteCapabilityRequest(in Uri contactUri, in List<String> remoteCapabilities, IOptionsRequestCallback cb); } diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java index a3be8dab2891..9293a40d9a33 100644 --- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java +++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java @@ -90,6 +90,30 @@ public interface CapabilityExchangeEventListener { void onUnpublish() throws ImsException; /** + * Notify the framework that the ImsService has refreshed the PUBLISH + * internally, which has resulted in a new PUBLISH result. + * <p> + * This method must return both SUCCESS (200 OK) and FAILURE (300+) codes in order to + * keep the AOSP stack up to date. + * @param reasonCode The SIP response code sent from the network. + * @param reasonPhrase The optional reason response from the network. If the + * network provided no reason with the sip code, the string should be empty. + * @param reasonHeaderCause The “cause” parameter of the “reason” header + * included in the SIP message. + * @param reasonHeaderText The “text” parameter of the “reason” header + * included in the SIP message. + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not + * currently connected to the framework. This can happen if the {@link RcsFeature} is not + * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received + * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare + * cases when the Telephony stack has crashed. + * + */ + default void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase, + int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException { + } + + /** * Inform the framework of an OPTIONS query from a remote device for this device's UCE * capabilities. * <p> diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 866fd2c7394e..ba9584112b75 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -530,6 +530,8 @@ public interface RILConstants { int RIL_REQUEST_GET_SLICING_CONFIG = 224; int RIL_REQUEST_ENABLE_VONR = 225; int RIL_REQUEST_IS_VONR_ENABLED = 226; + int RIL_REQUEST_SET_USAGE_SETTING = 227; + int RIL_REQUEST_GET_USAGE_SETTING = 228; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 5d0d718e15c3..3914ef6633b3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -231,7 +231,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti @Test fun screenLockedStart() { testSpec.assertLayersStart { - isVisible(colorFadComponent) + isEmpty() } } |