From 0b229eae8dc5c6c652fe5629b1aaeab0007a94cd Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Thu, 16 Sep 2021 14:42:12 +0200 Subject: [PATCH 1/3] Animate the internet dialog launch. This CL animates the launch of the internet dialog from the touch surface that started the dialog. Note that changes of the content of the dialog itself are not animated yet, this will be done in a follow-up CL. See b/201046726 for before/after videos. Bug: 201046726 Test: Manual Change-Id: Iaf45371eec5d8cf1f5c6a13ebf20474a0b565abc --- .../animation/DialogLaunchAnimator.kt | 39 +++++++++++++++++-- .../systemui/animation/LaunchableView.kt | 30 ++++++++++++++ .../drawable/internet_dialog_background.xml | 23 ----------- ...t_dialog_rounded_top_corner_background.xml | 22 ----------- .../layout/internet_connectivity_dialog.xml | 3 +- packages/SystemUI/res/values/dimens.xml | 1 - packages/SystemUI/res/values/styles.xml | 4 -- .../systemui/qs/tileimpl/QSTileViewImpl.kt | 37 +++++++++++++++++- .../systemui/qs/tiles/InternetTile.java | 2 +- .../qs/tiles/dialog/InternetDialog.java | 35 ++--------------- .../dialog/InternetDialogController.java | 18 ++++++++- .../qs/tiles/dialog/InternetDialogFactory.kt | 22 ++++++++--- .../connectivity/NetworkControllerImpl.java | 3 +- .../dialog/InternetDialogControllerTest.java | 10 +++-- 14 files changed, 148 insertions(+), 101 deletions(-) create mode 100644 packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt delete mode 100644 packages/SystemUI/res/drawable/internet_dialog_background.xml delete mode 100644 packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index c2b36089d0a7b..dbb5831c9d478 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -25,7 +25,11 @@ import android.view.Gravity import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver +import android.view.WindowInsets import android.view.WindowManager +import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR +import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN +import android.view.WindowManagerPolicyConstants import android.widget.FrameLayout private const val TAG = "DialogLaunchAnimator" @@ -252,6 +256,17 @@ private class DialogLaunchAnimation( WindowManager.LayoutParams.MATCH_PARENT ) + // If we are using gesture navigation, then we can overlay the navigation/task bars with + // the host dialog. + val navigationMode = context.resources.getInteger( + com.android.internal.R.integer.config_navBarInteractionMode) + if (navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL) { + window.attributes.fitInsetsTypes = window.attributes.fitInsetsTypes and + WindowInsets.Type.navigationBars().inv() + window.addFlags(FLAG_LAYOUT_IN_SCREEN or FLAG_LAYOUT_INSET_DECOR) + window.setDecorFitsSystemWindows(false) + } + // Prevent the host dialog from drawing until the animation starts. hostDialogRoot.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { @@ -356,15 +371,21 @@ private class DialogLaunchAnimation( dialogView.removeOnLayoutChangeListener(this) startAnimation( isLaunching = true, - onLaunchAnimationStart = { drawHostDialog = true }, + onLaunchAnimationStart = { + drawHostDialog = true + + // The ghost of the touch surface was just created, so the touch surface is + // currently invisible. We need to make sure that it stays invisible as long + // as the dialog is shown or animating. + if (touchSurface is LaunchableView) { + touchSurface.setShouldBlockVisibilityChanges(true) + } + }, onLaunchAnimationEnd = { touchSurface.setTag(R.id.launch_animation_running, null) // We hide the touch surface when the dialog is showing. We will make this // view visible again when dismissing the dialog. - // TODO(b/193634619): Provide an easy way for views to check if they should - // be hidden because of a dialog launch so that they don't override this - // visibility when updating/refreshing itself. touchSurface.visibility = View.INVISIBLE isLaunching = false @@ -417,6 +438,11 @@ private class DialogLaunchAnimation( if (!shouldAnimateDialogIntoView()) { Log.i(TAG, "Skipping animation of dialog into the touch surface") + // Make sure we allow the touch surface to change its visibility again. + if (touchSurface is LaunchableView) { + touchSurface.setShouldBlockVisibilityChanges(false) + } + // If the view is invisible it's probably because of us, so we make it visible again. if (touchSurface.visibility == View.INVISIBLE) { touchSurface.visibility = View.VISIBLE @@ -434,6 +460,11 @@ private class DialogLaunchAnimation( hostDialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) }, onLaunchAnimationEnd = { + // Make sure we allow the touch surface to change its visibility again. + if (touchSurface is LaunchableView) { + touchSurface.setShouldBlockVisibilityChanges(false) + } + touchSurface.visibility = View.VISIBLE originalDialogView!!.visibility = View.INVISIBLE dismissDialogs(true /* instantDismiss */) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt new file mode 100644 index 0000000000000..80a3eb839940a --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt @@ -0,0 +1,30 @@ +/* + * 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.animation + +/** A view that can expand/launch into an app or a dialog. */ +interface LaunchableView { + /** + * Set whether this view should block/prevent all visibility changes. This ensures that this + * view remains invisible during the launch animation given that it is ghosted and already drawn + * somewhere else. + * + * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and + * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked. + */ + fun setShouldBlockVisibilityChanges(block: Boolean) +} \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml deleted file mode 100644 index 3ceb0f6ac06ae..0000000000000 --- a/packages/SystemUI/res/drawable/internet_dialog_background.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml deleted file mode 100644 index 14672ef3dcfe9..0000000000000 --- a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 79ac737ba3041..f4faa62430dbb 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -20,8 +20,7 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/internet_connectivity_dialog" android:layout_width="@dimen/large_dialog_width" - android:layout_height="@dimen/internet_dialog_list_max_height" - android:background="@drawable/internet_dialog_rounded_top_corner_background" + android:layout_height="wrap_content" android:orientation="vertical"> 12dp - 662dp @dimen/match_parent diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index ff299eae8cf29..9bdd572a8eba6 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -956,10 +956,6 @@ ?android:attr/textColorPrimary - - diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 222539d495268..786081cf9c865 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -42,6 +42,7 @@ import androidx.annotation.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils import com.android.systemui.R +import com.android.systemui.animation.LaunchableView import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.BooleanState @@ -54,7 +55,7 @@ open class QSTileViewImpl @JvmOverloads constructor( context: Context, private val _icon: QSIconView, private val collapsed: Boolean = false -) : QSTileView(context), HeightOverrideable { +) : QSTileView(context), HeightOverrideable, LaunchableView { companion object { private const val INVALID = -1 @@ -118,6 +119,8 @@ open class QSTileViewImpl @JvmOverloads constructor( private var lastStateDescription: CharSequence? = null private var tileState = false private var lastState = INVALID + private var blockVisibilityChanges = false + private var lastVisibility = View.VISIBLE private val locInScreen = IntArray(2) @@ -294,6 +297,36 @@ open class QSTileViewImpl @JvmOverloads constructor( return sideView } + override fun setShouldBlockVisibilityChanges(block: Boolean) { + blockVisibilityChanges = block + + if (block) { + lastVisibility = visibility + } else { + visibility = lastVisibility + } + } + + override fun setVisibility(visibility: Int) { + if (blockVisibilityChanges) { + lastVisibility = visibility + return + } + + super.setVisibility(visibility) + } + + override fun setTransitionVisibility(visibility: Int) { + if (blockVisibilityChanges) { + // View.setTransitionVisibility just sets the visibility flag, so we don't have to save + // the transition visibility separately from the normal visibility. + lastVisibility = visibility + return + } + + super.setTransitionVisibility(visibility) + } + // Accessibility override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) { @@ -459,7 +492,7 @@ open class QSTileViewImpl @JvmOverloads constructor( } private fun setColor(color: Int) { - colorBackgroundDrawable.setTint(color) + colorBackgroundDrawable.mutate().setTint(color) paintColor = color } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 98d0a72685ba4..23b2a7642e364 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -124,7 +124,7 @@ public class InternetTile extends QSTileImpl { protected void handleClick(@Nullable View view) { mHandler.post(() -> mInternetDialogFactory.create(true, mAccessPointController.canConfigMobileData(), - mAccessPointController.canConfigWifi())); + mAccessPointController.canConfigWifi(), view)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 58e899285e3b2..dd03f6e35c453 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -15,14 +15,10 @@ */ package com.android.systemui.qs.tiles.dialog; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; - import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; import android.app.AlertDialog; import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Network; import android.net.NetworkCapabilities; @@ -42,9 +38,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; -import android.view.WindowInsets; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; @@ -130,7 +124,6 @@ public class InternetDialog extends SystemUIDialog implements private Switch mWiFiToggle; private FrameLayout mDoneLayout; private Drawable mBackgroundOn; - private int mListMaxHeight; private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private boolean mCanConfigMobileData; @@ -149,20 +142,11 @@ public class InternetDialog extends SystemUIDialog implements mInternetDialogSubTitle.setText(getSubtitleText()); }; - private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> { - // Set max height for list - if (mInternetDialogLayout.getHeight() > mListMaxHeight) { - ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams(); - params.height = mListMaxHeight; - mInternetDialogLayout.setLayoutParams(params); - } - }; - public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, InternetDialogController internetDialogController, boolean canConfigMobileData, boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger, @Main Handler handler, @Background Executor executor) { - super(context, R.style.Theme_SystemUI_Dialog_Internet); + super(context); if (DEBUG) { Log.d(TAG, "Init InternetDialog"); } @@ -184,8 +168,6 @@ public class InternetDialog extends SystemUIDialog implements return false; } }; - mListMaxHeight = context.getResources().getDimensionPixelSize( - R.dimen.internet_dialog_list_max_height); mUiEventLogger = uiEventLogger; mAdapter = new InternetAdapter(mInternetDialogController); if (!aboveStatusBar) { @@ -203,21 +185,12 @@ public class InternetDialog extends SystemUIDialog implements mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, null); final Window window = getWindow(); - final WindowManager.LayoutParams layoutParams = window.getAttributes(); - layoutParams.gravity = Gravity.BOTTOM; - // Move down the dialog to overlay the navigation bar. - layoutParams.setFitInsetsTypes( - layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars()); - layoutParams.setFitInsetsSides(WindowInsets.Side.all()); - layoutParams.setFitInsetsIgnoringVisibility(true); - window.setAttributes(layoutParams); window.setContentView(mDialogView); - //Only fix the width for large screen or tablet. + + // Only fix the width for large screen or tablet. window.setLayout(mContext.getResources().getDimensionPixelSize( R.dimen.large_dialog_width), ViewGroup.LayoutParams.WRAP_CONTENT); window.setWindowAnimations(R.style.Animation_InternetDialog); - window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - window.addFlags(FLAG_LAYOUT_NO_LIMITS); mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog); mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title); @@ -244,8 +217,6 @@ public class InternetDialog extends SystemUIDialog implements mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); - mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener( - mInternetListLayoutListener); mInternetDialogTitle.setText(getDialogTitleText()); mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); 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 40590a76536ee..5673136e18282 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 @@ -71,6 +71,7 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.settingslib.wifi.WifiUtils; import com.android.systemui.R; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -152,6 +153,7 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, private ToastFactory mToastFactory; private SignalDrawable mSignalDrawable; private LocationController mLocationController; + private DialogLaunchAnimator mDialogLaunchAnimator; @VisibleForTesting static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; @@ -202,7 +204,8 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, WindowManager windowManager, ToastFactory toastFactory, @Background Handler workerHandler, CarrierConfigTracker carrierConfigTracker, - LocationController locationController) { + LocationController locationController, + DialogLaunchAnimator dialogLaunchAnimator) { if (DEBUG) { Log.d(TAG, "Init InternetDialogController"); } @@ -231,6 +234,7 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, mToastFactory = toastFactory; mSignalDrawable = new SignalDrawable(mContext); mLocationController = locationController; + mDialogLaunchAnimator = dialogLaunchAnimator; } void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) { @@ -596,20 +600,32 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, } void launchNetworkSetting() { + // Dismissing a dialog into its touch surface and starting an activity at the same time + // looks bad, so let's make sure the dialog just fades out quickly. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mCallback.dismissDialog(); + mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0); } void launchWifiNetworkDetailsSetting(String key) { Intent intent = getWifiDetailsSettingsIntent(key); if (intent != null) { + // Dismissing a dialog into its touch surface and starting an activity at the same time + // looks bad, so let's make sure the dialog just fades out quickly. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mCallback.dismissDialog(); + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); } } void launchWifiScanningSetting() { + // Dismissing a dialog into its touch surface and starting an activity at the same time + // looks bad, so let's make sure the dialog just fades out quickly. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mCallback.dismissDialog(); + final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt index ea5df17bca58b..93828b3bcc997 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt @@ -18,9 +18,11 @@ package com.android.systemui.qs.tiles.dialog import android.content.Context import android.os.Handler import android.util.Log +import android.view.View import com.android.internal.logging.UiEventLogger +import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import java.util.concurrent.Executor import javax.inject.Inject @@ -37,14 +39,20 @@ class InternetDialogFactory @Inject constructor( @Background private val executor: Executor, private val internetDialogController: InternetDialogController, private val context: Context, - private val uiEventLogger: UiEventLogger + private val uiEventLogger: UiEventLogger, + private val dialogLaunchAnimator: DialogLaunchAnimator ) { companion object { var internetDialog: InternetDialog? = null } - /** Creates a [InternetDialog]. */ - fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) { + /** Creates a [InternetDialog]. The dialog will be animated from [view] if it is not null. */ + fun create( + aboveStatusBar: Boolean, + canConfigMobileData: Boolean, + canConfigWifi: Boolean, + view: View? + ) { if (internetDialog != null) { if (DEBUG) { Log.d(TAG, "InternetDialog is showing, do not create it twice.") @@ -54,7 +62,11 @@ class InternetDialogFactory @Inject constructor( internetDialog = InternetDialog(context, this, internetDialogController, canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler, executor) - internetDialog?.show() + if (view != null) { + dialogLaunchAnimator.showFromView(internetDialog!!, view) + } else { + internetDialog?.show() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index f72178f0c8b05..daae43f69d3bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -811,7 +811,8 @@ public class NetworkControllerImpl extends BroadcastReceiver break; case Settings.Panel.ACTION_INTERNET_CONNECTIVITY: mMainHandler.post(() -> mInternetDialogFactory.create(true, - mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi())); + mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi(), + null /* view */)); break; default: int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 5cea7632192b1..eb03b5ff2a6c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -45,6 +45,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.wifi.WifiUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -138,6 +139,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { private CarrierConfigTracker mCarrierConfigTracker; @Mock private LocationController mLocationController; + @Mock + private DialogLaunchAnimator mDialogLaunchAnimator; private TestableResources mTestableResources; private MockInternetDialogController mInternetDialogController; @@ -174,7 +177,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher, mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController, mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker, - mLocationController); + mLocationController, mDialogLaunchAnimator); mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, mInternetDialogController.mOnSubscriptionsChangedListener); mInternetDialogController.onStart(mInternetDialogCallback, true); @@ -654,12 +657,13 @@ public class InternetDialogControllerTest extends SysuiTestCase { KeyguardStateController keyguardStateController, WindowManager windowManager, ToastFactory toastFactory, Handler workerHandler, CarrierConfigTracker carrierConfigTracker, - LocationController locationController) { + LocationController locationController, + DialogLaunchAnimator dialogLaunchAnimator) { super(context, uiEventLogger, starter, accessPointController, subscriptionManager, telephonyManager, wifiManager, connectivityManager, handler, mainExecutor, broadcastDispatcher, keyguardUpdateMonitor, globalSettings, keyguardStateController, windowManager, toastFactory, workerHandler, - carrierConfigTracker, locationController); + carrierConfigTracker, locationController, dialogLaunchAnimator); mGlobalSettings = globalSettings; } From cd2a1b2ad01b1ff37076da1ad8266d1a7d5b1bab Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Mon, 11 Oct 2021 14:59:02 +0200 Subject: [PATCH 2/3] Synchronize dialog launch animations This CL adds some synchronization to the dialog launch animation to avoid flickering at the beginning and the end of the animation. It does so by drawing the touch surface twice (in both the original window and the dialog window) using a temporary ghost that is added/removed before/after the animation. I decided not to reuse the ghosts created by GhostedViewLaunchAnimatorController because this would make this change much more invasive, which I wanted to avoid given that the end goal is to use BLAST synchronization instead of this CL. Change-Id: Iac2eb2a2e78801a43847eebc72679c4952a73f1f Bug: 193634619 Test: Manual --- .../animation/DialogLaunchAnimator.kt | 169 +++++++++++++----- 1 file changed, 124 insertions(+), 45 deletions(-) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index dbb5831c9d478..669a054eaa2a7 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -21,10 +21,11 @@ import android.content.Context import android.graphics.Color import android.os.Looper import android.util.Log +import android.view.GhostView import android.view.Gravity import android.view.View import android.view.ViewGroup -import android.view.ViewTreeObserver +import android.view.ViewTreeObserver.OnPreDrawListener import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR @@ -225,10 +226,12 @@ private class DialogLaunchAnimation( private var isDismissing = false private var dismissRequested = false - private var drawHostDialog = false var ignoreNextCallToHide = false var exitAnimationDisabled = false + private var isTouchSurfaceGhostDrawn = false + private var isOriginalDialogViewLaidOut = false + fun start() { // Show the host (fullscreen) dialog, to which we will add the stolen dialog view. hostDialog.show() @@ -267,19 +270,65 @@ private class DialogLaunchAnimation( window.setDecorFitsSystemWindows(false) } - // Prevent the host dialog from drawing until the animation starts. - hostDialogRoot.viewTreeObserver.addOnPreDrawListener( - object : ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - if (drawHostDialog) { - hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this) - return true - } + // Disable the dim. We will enable it once we start the animation. + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - return false - } + // Add a temporary touch surface ghost as soon as the window is ready to draw. This + // temporary ghost will be drawn together with the touch surface, but in the host dialog + // window. Once it is drawn, we will make the touch surface invisible, and then start the + // animation. We do all this synchronization to avoid flicker that would occur if we made + // the touch surface invisible too early (before its ghost is drawn), leading to one or more + // frames with a hole instead of the touch surface (or its ghost). + hostDialogRoot.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { + override fun onPreDraw(): Boolean { + hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this) + addTemporaryTouchSurfaceGhost() + return true } - ) + }) + hostDialogRoot.invalidate() + } + + private fun addTemporaryTouchSurfaceGhost() { + // Create a ghost of the touch surface (which will make the touch surface invisible) and add + // it to the host dialog. We will wait for this ghost to be drawn before starting the + // animation. + val ghost = GhostView.addGhost(touchSurface, hostDialogRoot) + + // The ghost of the touch surface was just created, so the touch surface was made invisible. + // We make it visible again until the ghost is actually drawn. + touchSurface.visibility = View.VISIBLE + + // Wait for the ghost to be drawn before continuing. + ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { + override fun onPreDraw(): Boolean { + ghost.viewTreeObserver.removeOnPreDrawListener(this) + onTouchSurfaceGhostDrawn() + return true + } + }) + ghost.invalidate() + } + + private fun onTouchSurfaceGhostDrawn() { + // Make the touch surface invisible and make sure that it stays invisible as long as the + // dialog is shown or animating. + touchSurface.visibility = View.INVISIBLE + if (touchSurface is LaunchableView) { + touchSurface.setShouldBlockVisibilityChanges(true) + } + + // Add a pre draw listener to (maybe) start the animation once the touch surface is + // actually invisible. + touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { + override fun onPreDraw(): Boolean { + touchSurface.viewTreeObserver.removeOnPreDrawListener(this) + isTouchSurfaceGhostDrawn = true + maybeStartLaunchAnimation() + return true + } + }) + touchSurface.invalidate() } /** Get the content view of [originalDialog] and pass it to [then]. */ @@ -291,7 +340,7 @@ private class DialogLaunchAnimation( ?: throw IllegalStateException("Dialog does not have any android.R.id.content view") androidContent.viewTreeObserver.addOnPreDrawListener( - object : ViewTreeObserver.OnPreDrawListener { + object : OnPreDrawListener { override fun onPreDraw(): Boolean { if (androidContent.childCount == 1) { androidContent.viewTreeObserver.removeOnPreDrawListener(this) @@ -369,38 +418,47 @@ private class DialogLaunchAnimation( oldBottom: Int ) { dialogView.removeOnLayoutChangeListener(this) - startAnimation( - isLaunching = true, - onLaunchAnimationStart = { - drawHostDialog = true - // The ghost of the touch surface was just created, so the touch surface is - // currently invisible. We need to make sure that it stays invisible as long - // as the dialog is shown or animating. - if (touchSurface is LaunchableView) { - touchSurface.setShouldBlockVisibilityChanges(true) - } - }, - onLaunchAnimationEnd = { - touchSurface.setTag(R.id.launch_animation_running, null) - - // We hide the touch surface when the dialog is showing. We will make this - // view visible again when dismissing the dialog. - touchSurface.visibility = View.INVISIBLE - - isLaunching = false - - // dismiss was called during the animation, dismiss again now to actually - // dismiss. - if (dismissRequested) { - hostDialog.dismiss() - } - } - ) + isOriginalDialogViewLaidOut = true + maybeStartLaunchAnimation() } }) } + private fun maybeStartLaunchAnimation() { + if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) { + return + } + + // Show the background dim. + hostDialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + + startAnimation( + isLaunching = true, + onLaunchAnimationStart = { + // Remove the temporary ghost. Another ghost (that ghosts only the touch surface + // content, and not its background) will be added right after this and will be + // animated. + GhostView.removeGhost(touchSurface) + }, + onLaunchAnimationEnd = { + touchSurface.setTag(R.id.launch_animation_running, null) + + // We hide the touch surface when the dialog is showing. We will make this + // view visible again when dismissing the dialog. + touchSurface.visibility = View.INVISIBLE + + isLaunching = false + + // dismiss was called during the animation, dismiss again now to actually + // dismiss. + if (dismissRequested) { + hostDialog.dismiss() + } + } + ) + } + private fun onHostDialogDismissed(actualDismiss: () -> Unit) { if (Looper.myLooper() != Looper.getMainLooper()) { context.mainExecutor.execute { onHostDialogDismissed(actualDismiss) } @@ -467,8 +525,26 @@ private class DialogLaunchAnimation( touchSurface.visibility = View.VISIBLE originalDialogView!!.visibility = View.INVISIBLE - dismissDialogs(true /* instantDismiss */) - onDialogDismissed(this@DialogLaunchAnimation) + + // The animated ghost was just removed. We create a temporary ghost that will be + // removed only once we draw the touch surface, to avoid flickering that would + // happen when removing the ghost too early (before the touch surface is drawn). + GhostView.addGhost(touchSurface, hostDialogRoot) + + touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { + override fun onPreDraw(): Boolean { + touchSurface.viewTreeObserver.removeOnPreDrawListener(this) + + // Now that the touch surface was drawn, we can remove the temporary ghost + // and instantly dismiss the dialog. + GhostView.removeGhost(touchSurface) + dismissDialogs(true /* instantDismiss */) + onDialogDismissed(this@DialogLaunchAnimation) + + return true + } + }) + touchSurface.invalidate() } ) } @@ -503,10 +579,13 @@ private class DialogLaunchAnimation( } override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + // During launch, onLaunchAnimationStart will be used to remove the temporary touch + // surface ghost so it is important to call this before calling + // onLaunchAnimationStart on the controller (which will create its own ghost). + onLaunchAnimationStart() + startViewController.onLaunchAnimationStart(isExpandingFullyAbove) endViewController.onLaunchAnimationStart(isExpandingFullyAbove) - - onLaunchAnimationStart() } override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { From 4de63945eab26b78591e776acec98d7f0dca52c2 Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Mon, 18 Oct 2021 15:10:58 +0200 Subject: [PATCH 3/3] Make all SystemUIDialog's the same configurable width (1/2) This CL sets the default width of SystemUIDialog to be MATCH_PARENT on phones and 624dp on tablets/foldables. Moreover, as part of b/203389579, the width on tablets/foldables can be configured using a system property. That configurable width will be removed once b/203389579 is fixed, before the sc-v2 release. Bug: 203389579 Test: Manual Change-Id: I5a54914666415c9aa278d42492d9339480222df0 --- .../SystemUI/res/values-sw600dp/dimens.xml | 2 +- .../media/dialog/MediaOutputBaseDialog.java | 2 - .../android/systemui/privacy/PrivacyDialog.kt | 2 - .../qs/tiles/dialog/InternetDialog.java | 4 -- .../android/systemui/qs/user/UserDialog.kt | 4 -- .../statusbar/phone/SystemUIDialog.java | 43 +++++++++++++++++++ 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 85f8f0957a083..4e578615f30fa 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -105,5 +105,5 @@ 0dp - 624dp + 504dp diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 6895ef10fd07e..26ce645eefc58 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -104,8 +104,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements lp.setFitInsetsIgnoringVisibility(true); window.setAttributes(lp); window.setContentView(mDialogView); - window.setLayout(mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width), - ViewGroup.LayoutParams.WRAP_CONTENT); mHeaderTitle = mDialogView.requireViewById(R.id.header_title); mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle); diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt index 9e8f6b82c182d..23482677038c5 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt @@ -24,7 +24,6 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowInsets import android.widget.ImageView import android.widget.TextView @@ -65,7 +64,6 @@ class PrivacyDialog( window?.apply { attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() attributes.receiveInsetsIgnoringZOrder = true - setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT) setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index dd03f6e35c453..204dd46189d80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -37,7 +37,6 @@ import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; @@ -187,9 +186,6 @@ public class InternetDialog extends SystemUIDialog implements final Window window = getWindow(); window.setContentView(mDialogView); - // Only fix the width for large screen or tablet. - window.setLayout(mContext.getResources().getDimensionPixelSize( - R.dimen.large_dialog_width), ViewGroup.LayoutParams.WRAP_CONTENT); window.setWindowAnimations(R.style.Animation_InternetDialog); mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog); diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt index 01afa56fc4962..26d1bbde2a54a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt @@ -71,10 +71,6 @@ class UserDialog( setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() attributes.receiveInsetsIgnoringZOrder = true - setLayout( - context.resources.getDimensionPixelSize(R.dimen.notification_panel_width), - ViewGroup.LayoutParams.WRAP_CONTENT - ) setGravity(Gravity.CENTER) } setContentView(R.layout.qs_user_dialog_content) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 9415d5082d10b..18aa6893e7bc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -22,7 +22,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; +import android.os.SystemProperties; import android.os.UserHandle; +import android.util.TypedValue; +import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets.Type; import android.view.WindowManager; @@ -45,6 +49,10 @@ import java.util.Set; * and dismisses itself when it receives the broadcast. */ public class SystemUIDialog extends AlertDialog implements ListenableDialog { + // TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on. + private static final String FLAG_TABLET_DIALOG_WIDTH = + "persist.systemui.flag_tablet_dialog_width"; + private final Context mContext; private final DismissReceiver mDismissReceiver; private final Set mDialogListeners = new LinkedHashSet<>(); @@ -65,6 +73,41 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog { mDismissReceiver = new DismissReceiver(this); } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Set the dialog window size. + getWindow().setLayout(getDialogWidth(), ViewGroup.LayoutParams.WRAP_CONTENT); + } + + private int getDialogWidth() { + boolean isOnTablet = + mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600; + if (!isOnTablet) { + return ViewGroup.LayoutParams.MATCH_PARENT; + } + + int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0); + if (flagValue == -1) { + // The width of bottom sheets (624dp). + return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624, + mContext.getResources().getDisplayMetrics())); + } else if (flagValue == -2) { + // The suggested small width for all dialogs (348dp) + return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348, + mContext.getResources().getDisplayMetrics())); + } else if (flagValue > 0) { + // Any given width. + return Math.round( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue, + mContext.getResources().getDisplayMetrics())); + } else { + // By default we use the same width as the notification shade in portrait mode (504dp). + return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width); + } + } + @Override protected void onStart() { super.onStart();