diff options
Diffstat (limited to 'packages/SystemUI/src')
90 files changed, 2790 insertions, 1113 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 4b71a3a8fbdc..baf34589770c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -19,42 +19,22 @@ package com.android.keyguard; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.app.PendingIntent; import android.app.WallpaperManager; -import android.app.smartspace.SmartspaceConfig; -import android.app.smartspace.SmartspaceManager; -import android.app.smartspace.SmartspaceSession; -import android.app.smartspace.SmartspaceTarget; -import android.content.Intent; -import android.content.pm.UserInfo; import android.content.res.Resources; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateFormat; import android.view.View; import android.widget.FrameLayout; import android.widget.RelativeLayout; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.clock.ClockManager; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.BcSmartspaceDataPlugin; -import com.android.systemui.plugins.BcSmartspaceDataPlugin.IntentStarter; import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -62,14 +42,10 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; -import com.android.systemui.util.settings.SecureSettings; import java.util.Locale; -import java.util.Optional; import java.util.TimeZone; -import java.util.concurrent.Executor; import javax.inject.Inject; @@ -85,9 +61,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final KeyguardSliceViewController mKeyguardSliceViewController; private final NotificationIconAreaController mNotificationIconAreaController; private final BroadcastDispatcher mBroadcastDispatcher; - private final Executor mUiExecutor; private final BatteryController mBatteryController; - private final FeatureFlags mFeatureFlags; + private final LockscreenSmartspaceController mSmartspaceController; /** * Clock for both small and large sizes @@ -97,20 +72,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private AnimatableClockController mLargeClockViewController; private FrameLayout mLargeClockFrame; - private SmartspaceSession mSmartspaceSession; - private SmartspaceSession.OnTargetsAvailableListener mSmartspaceCallback; - private ConfigurationController mConfigurationController; - private ActivityStarter mActivityStarter; - private FalsingManager mFalsingManager; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final KeyguardBypassController mBypassController; - private Handler mHandler; - private UserTracker mUserTracker; - private SecureSettings mSecureSettings; - private ContentObserver mSettingsObserver; - private boolean mShowSensitiveContentForCurrentUser; - private boolean mShowSensitiveContentForManagedUser; - private UserHandle mManagedUserHandle; /** * Listener for changes to the color palette. @@ -118,59 +81,30 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS * The color palette changes when the wallpaper is changed. */ private final ColorExtractor.OnColorsChangedListener mColorsListener = - new ColorExtractor.OnColorsChangedListener() { - @Override - public void onColorsChanged(ColorExtractor extractor, int which) { - if ((which & WallpaperManager.FLAG_LOCK) != 0) { - mView.updateColors(getGradientColors()); - } - } - }; - - private final ConfigurationController.ConfigurationListener mConfigurationListener = - new ConfigurationController.ConfigurationListener() { - @Override - public void onThemeChanged() { - updateWallpaperColor(); - } - }; - - private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; - - private final StatusBarStateController.StateListener mStatusBarStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onDozeAmountChanged(float linear, float eased) { - if (mSmartspaceView != null) { - mSmartspaceView.setDozeAmount(eased); - } + (extractor, which) -> { + if ((which & WallpaperManager.FLAG_LOCK) != 0) { + mView.updateColors(getGradientColors()); } }; + private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; + // If set, will replace keyguard_status_area - private BcSmartspaceDataPlugin.SmartspaceView mSmartspaceView; - private Optional<BcSmartspaceDataPlugin> mSmartspacePlugin; + private View mSmartspaceView; @Inject public KeyguardClockSwitchController( KeyguardClockSwitch keyguardClockSwitch, StatusBarStateController statusBarStateController, - SysuiColorExtractor colorExtractor, ClockManager clockManager, + SysuiColorExtractor colorExtractor, + ClockManager clockManager, KeyguardSliceViewController keyguardSliceViewController, NotificationIconAreaController notificationIconAreaController, BroadcastDispatcher broadcastDispatcher, - FeatureFlags featureFlags, - @Main Executor uiExecutor, BatteryController batteryController, - ConfigurationController configurationController, - ActivityStarter activityStarter, - FalsingManager falsingManager, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardBypassController bypassController, - @Main Handler handler, - UserTracker userTracker, - SecureSettings secureSettings, - Optional<BcSmartspaceDataPlugin> smartspacePlugin) { + LockscreenSmartspaceController smartspaceController) { super(keyguardClockSwitch); mStatusBarStateController = statusBarStateController; mColorExtractor = colorExtractor; @@ -178,18 +112,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardSliceViewController = keyguardSliceViewController; mNotificationIconAreaController = notificationIconAreaController; mBroadcastDispatcher = broadcastDispatcher; - mFeatureFlags = featureFlags; - mUiExecutor = uiExecutor; mBatteryController = batteryController; - mConfigurationController = configurationController; - mActivityStarter = activityStarter; - mFalsingManager = falsingManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mBypassController = bypassController; - mHandler = handler; - mUserTracker = userTracker; - mSecureSettings = secureSettings; - mSmartspacePlugin = smartspacePlugin; + mSmartspaceController = smartspaceController; } /** @@ -232,119 +158,33 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mBypassController); mLargeClockViewController.init(); - mStatusBarStateController.addCallback(mStatusBarStateListener); - mConfigurationController.addCallback(mConfigurationListener); + if (mSmartspaceController.isEnabled()) { + mSmartspaceView = mSmartspaceController.buildAndConnectView(mView); - if (mFeatureFlags.isSmartspaceEnabled() && mSmartspacePlugin.isPresent()) { - BcSmartspaceDataPlugin smartspaceDataPlugin = mSmartspacePlugin.get(); View ksa = mView.findViewById(R.id.keyguard_status_area); int ksaIndex = mView.indexOfChild(ksa); ksa.setVisibility(View.GONE); - mSmartspaceView = smartspaceDataPlugin.getView(mView); - mSmartspaceView.registerDataProvider(smartspaceDataPlugin); - mSmartspaceView.setIntentStarter(new IntentStarter() { - public void startIntent(View v, Intent i) { - mActivityStarter.startActivity(i, true /* dismissShade */); - } - - public void startPendingIntent(PendingIntent pi) { - mActivityStarter.startPendingIntentDismissingKeyguard(pi); - } - }); - mSmartspaceView.setFalsingManager(mFalsingManager); - updateWallpaperColor(); - View asView = (View) mSmartspaceView; - // Place smartspace view below normal clock... RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( MATCH_PARENT, WRAP_CONTENT); lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view); - mView.addView(asView, ksaIndex, lp); + mView.addView(mSmartspaceView, ksaIndex, lp); int padding = getContext().getResources() .getDimensionPixelSize(R.dimen.below_clock_padding_start); - asView.setPadding(padding, 0, padding, 0); + mSmartspaceView.setPadding(padding, 0, padding, 0); // ... but above the large clock lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); - lp.addRule(RelativeLayout.BELOW, asView.getId()); + lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId()); mLargeClockFrame.setLayoutParams(lp); View nic = mView.findViewById( R.id.left_aligned_notification_icon_container); lp = (RelativeLayout.LayoutParams) nic.getLayoutParams(); - lp.addRule(RelativeLayout.BELOW, asView.getId()); + lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId()); nic.setLayoutParams(lp); - - mSmartspaceSession = getContext().getSystemService(SmartspaceManager.class) - .createSmartspaceSession( - new SmartspaceConfig.Builder(getContext(), "lockscreen").build()); - mSmartspaceCallback = targets -> { - targets.removeIf(this::filterSmartspaceTarget); - smartspaceDataPlugin.onTargetsAvailable(targets); - }; - mSmartspaceSession.addOnTargetsAvailableListener(mUiExecutor, mSmartspaceCallback); - mSettingsObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange, Uri uri) { - reloadSmartspace(); - } - }; - - getContext().getContentResolver().registerContentObserver( - Settings.Secure.getUriFor( - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), - true, mSettingsObserver, UserHandle.USER_ALL); - reloadSmartspace(); - } - - float dozeAmount = mStatusBarStateController.getDozeAmount(); - mStatusBarStateListener.onDozeAmountChanged(dozeAmount, dozeAmount); - } - - @VisibleForTesting - boolean filterSmartspaceTarget(SmartspaceTarget t) { - if (!t.isSensitive()) return false; - - if (t.getUserHandle().equals(mUserTracker.getUserHandle())) { - return !mShowSensitiveContentForCurrentUser; - } - if (t.getUserHandle().equals(mManagedUserHandle)) { - return !mShowSensitiveContentForManagedUser; - } - - return false; - } - - private void reloadSmartspace() { - mManagedUserHandle = getWorkProfileUser(); - final String setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; - - mShowSensitiveContentForCurrentUser = - mSecureSettings.getIntForUser(setting, 0, mUserTracker.getUserId()) == 1; - if (mManagedUserHandle != null) { - int id = mManagedUserHandle.getIdentifier(); - mShowSensitiveContentForManagedUser = - mSecureSettings.getIntForUser(setting, 0, id) == 1; - } - - mSmartspaceSession.requestSmartspaceUpdate(); - } - - private UserHandle getWorkProfileUser() { - for (UserInfo userInfo : mUserTracker.getUserProfiles()) { - if (userInfo.isManagedProfile()) { - return userInfo.getUserHandle(); - } - } - return null; - } - - private void updateWallpaperColor() { - if (mSmartspaceView != null) { - int color = Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColor); - mSmartspaceView.setPrimaryTextColor(color); } } @@ -356,16 +196,16 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mColorExtractor.removeOnColorsChangedListener(mColorsListener); mView.setClockPlugin(null, mStatusBarStateController.getState()); - if (mSmartspaceSession != null) { - mSmartspaceSession.removeOnTargetsAvailableListener(mSmartspaceCallback); - mSmartspaceSession.close(); - mSmartspaceSession = null; - } - mStatusBarStateController.removeCallback(mStatusBarStateListener); - mConfigurationController.removeCallback(mConfigurationListener); + mSmartspaceController.disconnect(); - if (mSettingsObserver != null) { - getContext().getContentResolver().unregisterContentObserver(mSettingsObserver); + // TODO: This is an unfortunate necessity since smartspace plugin retains a single instance + // of the smartspace view -- if we don't remove the view, it can't be reused by a later + // instance of this class. In order to fix this, we need to modify the plugin so that + // (a) we get a new view each time and (b) we can properly clean up an old view by making + // it unregister itself as a plugin listener. + if (mSmartspaceView != null) { + mView.removeView(mSmartspaceView); + mSmartspaceView = null; } } @@ -436,7 +276,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS scale, props, animate); if (mSmartspaceView != null) { - PropertyAnimator.setProperty((View) mSmartspaceView, AnimatableProperty.TRANSLATION_X, + PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X, x, props, animate); } @@ -510,14 +350,4 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private int getCurrentLayoutDirection() { return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); } - - @VisibleForTesting - ConfigurationController.ConfigurationListener getConfigurationListener() { - return mConfigurationListener; - } - - @VisibleForTesting - ContentObserver getSettingsObserver() { - return mSettingsObserver; - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 3d42da2e5158..97d3a5a4cd18 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -28,7 +28,6 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -167,7 +166,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final TelephonyManager mTelephonyManager; private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final FalsingCollector mFalsingCollector; - private final boolean mIsNewLayoutEnabled; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -177,8 +175,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, @Main Resources resources, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, - EmergencyButtonController.Factory emergencyButtonControllerFactory, - FeatureFlags featureFlags) { + EmergencyButtonController.Factory emergencyButtonControllerFactory) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -190,7 +187,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mTelephonyManager = telephonyManager; mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; mFalsingCollector = falsingCollector; - mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled(); } /** Create a new {@link KeyguardInputViewController}. */ @@ -216,20 +212,19 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, emergencyButtonController, mFalsingCollector, - mIsNewLayoutEnabled); + mLiftToActivateListener, emergencyButtonController, mFalsingCollector); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mIsNewLayoutEnabled); + emergencyButtonController); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mIsNewLayoutEnabled); + emergencyButtonController); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 09fb8efba4e8..0b8868f6d3c4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -169,20 +169,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } /** - * By default, the new layout will be enabled. When false, revert to the old style. - */ - public void setIsNewLayoutEnabled(boolean isEnabled) { - if (!isEnabled) { - for (int i = 0; i < mButtons.length; i++) { - mButtons[i].disableNewLayout(); - } - mDeleteButton.disableNewLayout(); - mOkButton.disableNewLayout(); - reloadColors(); - } - } - - /** * Reload colors from resources. **/ public void reloadColors() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index a456d42f5be5..262bed3f695c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -35,12 +35,11 @@ public class KeyguardPinViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, - FalsingCollector falsingCollector, boolean isNewLayoutEnabled) { + FalsingCollector falsingCollector) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; - view.setIsNewLayoutEnabled(isNewLayoutEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 90d191f0f418..90d140eabb20 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -80,14 +80,13 @@ public class KeyguardSimPinViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, - EmergencyButtonController emergencyButtonController, boolean isNewLayoutEnabled) { + EmergencyButtonController emergencyButtonController) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); - view.setIsNewLayoutEnabled(isNewLayoutEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 6e868b9f0ab6..ff680a56efe7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -85,14 +85,13 @@ public class KeyguardSimPukViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, - EmergencyButtonController emergencyButtonController, boolean isNewLayoutEnabled) { + EmergencyButtonController emergencyButtonController) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); - view.setIsNewLayoutEnabled(isNewLayoutEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index abdd770c37d4..e6298a426c5b 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.view.ContextThemeWrapper; @@ -40,17 +39,14 @@ class NumPadAnimator { private ValueAnimator mContractAnimator; private GradientDrawable mBackground; private RippleDrawable mRipple; - private GradientDrawable mRippleMask; private int mNormalColor; private int mHighlightColor; private int mStyle; - NumPadAnimator(Context context, LayerDrawable drawable, @StyleRes int style) { - LayerDrawable ld = (LayerDrawable) drawable.mutate(); - mBackground = (GradientDrawable) ld.findDrawableByLayerId(R.id.background); - mRipple = (RippleDrawable) ld.findDrawableByLayerId(R.id.ripple); - mRippleMask = (GradientDrawable) mRipple.findDrawableByLayerId(android.R.id.mask); + NumPadAnimator(Context context, final RippleDrawable drawable, @StyleRes int style) { mStyle = style; + mRipple = (RippleDrawable) drawable.mutate(); + mBackground = (GradientDrawable) mRipple.findDrawableByLayerId(R.id.background); reloadColors(context); @@ -62,7 +58,7 @@ class NumPadAnimator { mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator anim) { mBackground.setCornerRadius((float) anim.getAnimatedValue()); - mRippleMask.setCornerRadius((float) anim.getAnimatedValue()); + mRipple.invalidateSelf(); } }); @@ -73,7 +69,7 @@ class NumPadAnimator { mContractAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator anim) { mBackground.setCornerRadius((float) anim.getAnimatedValue()); - mRippleMask.setCornerRadius((float) anim.getAnimatedValue()); + mRipple.invalidateSelf(); } }); mAnimator.playSequentially(mExpandAnimator, mContractAnimator); diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index b76499a39ff8..096597afa889 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -16,37 +16,22 @@ package com.android.keyguard; import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.VectorDrawable; +import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; -import android.view.ContextThemeWrapper; import android.view.MotionEvent; -import androidx.annotation.Nullable; - -import com.android.settingslib.Utils; -import com.android.systemui.R; - /** * Similar to the {@link NumPadKey}, but displays an image. */ public class NumPadButton extends AlphaOptimizedImageButton { - @Nullable private NumPadAnimator mAnimator; public NumPadButton(Context context, AttributeSet attrs) { super(context, attrs); - Drawable background = getBackground(); - if (background instanceof LayerDrawable) { - mAnimator = new NumPadAnimator(context, (LayerDrawable) background, - attrs.getStyleAttribute()); - } else { - mAnimator = null; - } + mAnimator = new NumPadAnimator(context, (RippleDrawable) getBackground(), + attrs.getStyleAttribute()); } @Override @@ -56,7 +41,7 @@ public class NumPadButton extends AlphaOptimizedImageButton { // Set width/height to the same value to ensure a smooth circle for the bg, but shrink // the height to match the old pin bouncer int width = getMeasuredWidth(); - int height = mAnimator == null ? (int) (width * .75f) : width; + int height = width; setMeasuredDimension(getMeasuredWidth(), height); } @@ -65,13 +50,13 @@ public class NumPadButton extends AlphaOptimizedImageButton { protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (mAnimator != null) mAnimator.onLayout(b - t); + mAnimator.onLayout(b - t); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - if (mAnimator != null) mAnimator.start(); + mAnimator.start(); } return super.onTouchEvent(event); } @@ -80,25 +65,6 @@ public class NumPadButton extends AlphaOptimizedImageButton { * Reload colors from resources. **/ public void reloadColors() { - if (mAnimator != null) { - mAnimator.reloadColors(getContext()); - } else { - // Needed for old style pin - int textColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary) - .getDefaultColor(); - ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor)); - } - } - - /** - * By default, the new layout will be enabled. Invoking will revert to the old style - */ - public void disableNewLayout() { - if (mAnimator != null) { - mAnimator = null; - ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey); - setBackground(getContext().getResources().getDrawable( - R.drawable.ripple_drawable_pin, ctw.getTheme())); - } + mAnimator.reloadColors(getContext()); } } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index 89c1a7fe21ca..35ace0d1a404 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -18,12 +18,10 @@ package com.android.keyguard; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.RippleDrawable; import android.os.PowerManager; import android.os.SystemClock; import android.util.AttributeSet; -import android.view.ContextThemeWrapper; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -32,8 +30,6 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.TextView; -import androidx.annotation.Nullable; - import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.Utils; import com.android.systemui.R; @@ -51,7 +47,6 @@ public class NumPadKey extends ViewGroup { private int mTextViewResId; private PasswordTextView mTextView; - @Nullable private NumPadAnimator mAnimator; private View.OnClickListener mListener = new View.OnClickListener() { @@ -131,26 +126,8 @@ public class NumPadKey extends ViewGroup { setContentDescription(mDigitText.getText().toString()); - Drawable background = getBackground(); - if (background instanceof LayerDrawable) { - mAnimator = new NumPadAnimator(context, (LayerDrawable) background, - R.style.NumPadKey); - } else { - mAnimator = null; - } - } - - /** - * By default, the new layout will be enabled. Invoking will revert to the old style - */ - public void disableNewLayout() { - findViewById(R.id.klondike_text).setVisibility(View.VISIBLE); - if (mAnimator != null) { - mAnimator = null; - ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey); - setBackground(getContext().getResources().getDrawable( - R.drawable.ripple_drawable_pin, ctw.getTheme())); - } + mAnimator = new NumPadAnimator(context, (RippleDrawable) getBackground(), + R.style.NumPadKey); } /** @@ -164,14 +141,14 @@ public class NumPadKey extends ViewGroup { mDigitText.setTextColor(textColor); mKlondikeText.setTextColor(klondikeColor); - if (mAnimator != null) mAnimator.reloadColors(getContext()); + mAnimator.reloadColors(getContext()); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { doHapticKeyClick(); - if (mAnimator != null) mAnimator.start(); + mAnimator.start(); } return super.onTouchEvent(event); @@ -185,7 +162,7 @@ public class NumPadKey extends ViewGroup { // Set width/height to the same value to ensure a smooth circle for the bg, but shrink // the height to match the old pin bouncer int width = getMeasuredWidth(); - int height = mAnimator == null ? (int) (width * .75f) : width; + int height = width; setMeasuredDimension(getMeasuredWidth(), height); } @@ -206,7 +183,7 @@ public class NumPadKey extends ViewGroup { left = centerX - mKlondikeText.getMeasuredWidth() / 2; mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom); - if (mAnimator != null) mAnimator.onLayout(b - t); + mAnimator.onLayout(b - t); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 76cec0b33477..64a683e78953 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -305,10 +305,15 @@ public class ImageWallpaper extends WallpaperService { continue; } Rect subImage = new Rect( - Math.round(area.left * b.getWidth()), - Math.round(area.top * b.getHeight()), - Math.round(area.right * b.getWidth()), - Math.round(area.bottom * b.getHeight())); + (int) Math.floor(area.left * b.getWidth()), + (int) Math.floor(area.top * b.getHeight()), + (int) Math.ceil(area.right * b.getWidth()), + (int) Math.ceil(area.bottom * b.getHeight())); + if (subImage.isEmpty()) { + // Do not notify client. treat it as too small to sample + colors.add(null); + continue; + } Bitmap colorImg = Bitmap.createBitmap(b, subImage.left, subImage.top, subImage.width(), subImage.height()); WallpaperColors color = WallpaperColors.fromBitmap(colorImg); diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 7f183798709c..bf09975bb034 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -73,7 +73,8 @@ public final class Prefs { Key.TOUCHED_RINGER_TOGGLE, Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, Key.HAS_SEEN_REVERSE_BOTTOM_SHEET, - Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT + Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT, + Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP }) // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them public @interface Key { @@ -122,6 +123,8 @@ public final class Prefs { String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip"; String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; + String HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP = + "HasSeenAccessibilityFloatingMenuDockTooltip"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 4b8ec5022573..0213335d6372 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -55,8 +55,6 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.os.Handler; -import android.os.HandlerExecutor; -import android.os.HandlerThread; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Secure; @@ -91,11 +89,13 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -126,7 +126,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { @VisibleForTesting protected boolean mIsRegistered; private final BroadcastDispatcher mBroadcastDispatcher; - private final Handler mMainHandler; + private final Executor mMainExecutor; private final TunerService mTunerService; private final SecureSettings mSecureSettings; private DisplayManager.DisplayListener mDisplayListener; @@ -157,6 +157,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private WindowManager mWindowManager; private int mRotation; private SecureSetting mColorInversionSetting; + private DelayableExecutor mExecutor; private Handler mHandler; private boolean mPendingRotationChange; private boolean mIsRoundedCornerMultipleRadius; @@ -216,7 +217,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { @Inject public ScreenDecorations(Context context, - @Main Handler handler, + @Main Executor mainExecutor, SecureSettings secureSettings, BroadcastDispatcher broadcastDispatcher, TunerService tunerService, @@ -224,7 +225,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { PrivacyDotViewController dotViewController, ThreadFactory threadFactory) { super(context); - mMainHandler = handler; + mMainExecutor = mainExecutor; mSecureSettings = secureSettings; mBroadcastDispatcher = broadcastDispatcher; mTunerService = tunerService; @@ -239,17 +240,10 @@ public class ScreenDecorations extends SystemUI implements Tunable { Log.i(TAG, "ScreenDecorations is disabled"); return; } - mHandler = startHandlerThread(); - mHandler.post(this::startOnScreenDecorationsThread); - mDotViewController.setUiExecutor( - mThreadFactory.buildDelayableExecutorOnLooper(mHandler.getLooper())); - } - - @VisibleForTesting - Handler startHandlerThread() { - HandlerThread thread = new HandlerThread("ScreenDecorations"); - thread.start(); - return thread.getThreadHandler(); + mHandler = mThreadFactory.builderHandlerOnNewThread("ScreenDecorations"); + mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler); + mExecutor.execute(this::startOnScreenDecorationsThread); + mDotViewController.setUiExecutor(mExecutor); } private void startOnScreenDecorationsThread() { @@ -336,7 +330,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics); mDensity = metrics.density; - mMainHandler.post(() -> mTunerService.addTunable(this, SIZE)); + mExecutor.execute(() -> mTunerService.addTunable(this, SIZE)); // Watch color inversion and invert the overlay as needed. if (mColorInversionSetting == null) { @@ -355,10 +349,10 @@ public class ScreenDecorations extends SystemUI implements Tunable { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter, - new HandlerExecutor(mHandler), UserHandle.ALL); + mExecutor, UserHandle.ALL); mIsRegistered = true; } else { - mMainHandler.post(() -> mTunerService.removeTunable(this)); + mMainExecutor.execute(() -> mTunerService.removeTunable(this)); if (mColorInversionSetting != null) { mColorInversionSetting.setListening(false); @@ -571,7 +565,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { Resources res = mContext.getResources(); boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection); if (enabled) { - mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mHandler::post); + mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mExecutor); mCameraListener.addTransitionCallback(mCameraTransitionCallback); mCameraListener.startListening(); } @@ -628,7 +622,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { Log.i(TAG, "ScreenDecorations is disabled"); return; } - mHandler.post(() -> { + mExecutor.execute(() -> { int oldRotation = mRotation; mPendingRotationChange = false; updateOrientation(); @@ -836,7 +830,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { Log.i(TAG, "ScreenDecorations is disabled"); return; } - mHandler.post(() -> { + mExecutor.execute(() -> { if (mOverlays == null) return; if (SIZE.equals(key)) { Point size = mRoundedDefault; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java b/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java index b69001d1ba07..c472457b0206 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java @@ -63,17 +63,15 @@ abstract class DisplayIdIndexSupplier<T> { } /** - * Gets the object by the element index. + * Returns the object with the given display id. * - * <p> If the index is bigger than the array size, an {@link ArrayIndexOutOfBoundsException} is - * thrown for apps targeting {@link android.os.Build.VERSION_CODES#Q} and later </p> * - * @param index the element index + * @param displayId the logical display Id * @return T - * @see SparseArray#valueAt(int) */ - public T valueAt(int index) { - return mSparseArray.valueAt(index); + @Nullable + public T valueAt(int displayId) { + return mSparseArray.get(displayId); } @NonNull diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java index 4c892e29f386..4b30ec3e6f6f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java @@ -113,7 +113,7 @@ class MagnificationGestureDetector { final float rawX = event.getRawX(); final float rawY = event.getRawY(); boolean handled = false; - switch (event.getAction()) { + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mPointerDown.set(rawX, rawY); mHandler.postAtTime(mCancelTapGestureRunnable, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 4f5fdc90e929..cee395bd9a6a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -18,10 +18,11 @@ package com.android.systemui.accessibility; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; + import android.annotation.MainThread; import android.annotation.Nullable; import android.content.Context; -import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; @@ -37,9 +38,13 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.model.SysUiState; +import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; +import java.io.FileDescriptor; +import java.io.PrintWriter; + import javax.inject.Inject; /** @@ -52,34 +57,33 @@ import javax.inject.Inject; public class WindowMagnification extends SystemUI implements WindowMagnifierCallback, CommandQueue.Callbacks { private static final String TAG = "WindowMagnification"; - private static final int CONFIG_MASK = - ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION - | ActivityInfo.CONFIG_LOCALE; private final ModeSwitchesController mModeSwitchesController; private final Handler mHandler; private final AccessibilityManager mAccessibilityManager; private final CommandQueue mCommandQueue; + private final OverviewProxyService mOverviewProxyService; private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl; private Configuration mLastConfiguration; + private SysUiState mSysUiState; private static class AnimationControllerSupplier extends DisplayIdIndexSupplier<WindowMagnificationAnimationController> { private final Context mContext; private final Handler mHandler; - private final NavigationModeController mNavigationModeController; private final WindowMagnifierCallback mWindowMagnifierCallback; + private final SysUiState mSysUiState; AnimationControllerSupplier(Context context, Handler handler, - NavigationModeController navigationModeController, - WindowMagnifierCallback windowMagnifierCallback, DisplayManager displayManager) { + WindowMagnifierCallback windowMagnifierCallback, + DisplayManager displayManager, SysUiState sysUiState) { super(displayManager); mContext = context; mHandler = handler; - mNavigationModeController = navigationModeController; mWindowMagnifierCallback = windowMagnifierCallback; + mSysUiState = sysUiState; } @Override @@ -89,10 +93,7 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall final WindowMagnificationController controller = new WindowMagnificationController( mContext, mHandler, new SfVsyncFrameCallbackProvider(), null, - new SurfaceControl.Transaction(), mWindowMagnifierCallback); - final int navBarMode = mNavigationModeController.addListener( - controller::onNavigationModeChanged); - controller.onNavigationModeChanged(navBarMode); + new SurfaceControl.Transaction(), mWindowMagnifierCallback, mSysUiState); return new WindowMagnificationAnimationController(windowContext, controller); } } @@ -103,24 +104,22 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall @Inject public WindowMagnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, - NavigationModeController navigationModeController) { + SysUiState sysUiState, OverviewProxyService overviewProxyService) { super(context); mHandler = mainHandler; mLastConfiguration = new Configuration(context.getResources().getConfiguration()); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; mModeSwitchesController = modeSwitchesController; + mSysUiState = sysUiState; + mOverviewProxyService = overviewProxyService; mAnimationControllerSupplier = new AnimationControllerSupplier(context, - mHandler, navigationModeController, this, - context.getSystemService(DisplayManager.class)); + mHandler, this, context.getSystemService(DisplayManager.class), sysUiState); } @Override public void onConfigurationChanged(Configuration newConfig) { final int configDiff = newConfig.diff(mLastConfiguration); - if ((configDiff & CONFIG_MASK) == 0) { - return; - } mLastConfiguration.setTo(newConfig); mAnimationControllerSupplier.forEach( animationController -> animationController.onConfigurationChanged(configDiff)); @@ -132,6 +131,28 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall @Override public void start() { mCommandQueue.addCallback(this); + mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { + @Override + public void onConnectionChanged(boolean isConnected) { + if (isConnected) { + updateSysUiStateFlag(); + } + } + }); + } + + private void updateSysUiStateFlag() { + //TODO(b/187510533): support multi-display once SysuiState supports it. + final WindowMagnificationAnimationController controller = + mAnimationControllerSupplier.valueAt(Display.DEFAULT_DISPLAY); + if (controller != null) { + controller.updateSysUiStateFlag(); + } else { + // The instance is initialized when there is an IPC request. Considering + // self-crash cases, we need to reset the flag in such situation. + mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false) + .commitUpdate(Display.DEFAULT_DISPLAY); + } } @MainThread @@ -210,6 +231,13 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall } } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(TAG); + mAnimationControllerSupplier.forEach( + animationController -> animationController.dump(pw)); + } + private void setWindowMagnificationConnection() { if (mWindowMagnificationConnectionImpl == null) { mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java index 5758b1575f5a..36fef3ed6b51 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java @@ -31,6 +31,7 @@ import android.view.animation.AccelerateInterpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -269,6 +270,14 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp mController.enableWindowMagnification(sentScale, centerX, centerY); } + public void updateSysUiStateFlag() { + mController.updateSysUIStateFlag(); + } + + void dump(PrintWriter pw) { + mController.dump(pw); + } + private static ValueAnimator newValueAnimator(Resources resources) { final ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setDuration( diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index ae16703405a5..fcb090a76d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -16,9 +16,10 @@ package com.android.systemui.accessibility; +import static android.view.WindowInsets.Type.systemGestures; import static android.view.WindowManager.LayoutParams; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -28,6 +29,7 @@ import android.annotation.UiContext; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -52,14 +54,17 @@ import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.view.WindowMetrics; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.R; +import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.WindowManagerWrapper; +import java.io.PrintWriter; import java.text.NumberFormat; import java.util.Locale; @@ -111,6 +116,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final View.OnLayoutChangeListener mMirrorSurfaceViewLayoutChangeListener; private final Runnable mMirrorViewRunnable; private final Runnable mUpdateStateDescriptionRunnable; + private final Runnable mWindowInsetChangeRunnable; private View mMirrorView; private SurfaceView mMirrorSurfaceView; private int mMirrorSurfaceMargin; @@ -119,9 +125,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private int mOuterBorderSize; // The boundary of magnification frame. private final Rect mMagnificationFrameBoundary = new Rect(); - - private int mNavBarMode; - private int mNavGestureHeight; + // The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid. + private int mSystemGestureTop = -1; private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private final MagnificationGestureDetector mGestureDetector; @@ -130,6 +135,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private Locale mLocale; private NumberFormat mPercentFormat; private float mBounceEffectAnimationScale; + private SysUiState mSysUiState; + // Set it to true when the view is overlapped with the gesture insets at the bottom. + private boolean mOverlapWithGestureInsets; @Nullable private MirrorWindowControl mMirrorWindowControl; @@ -137,11 +145,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold WindowMagnificationController(@UiContext Context context, @NonNull Handler handler, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction, - @NonNull WindowMagnifierCallback callback) { + @NonNull WindowMagnifierCallback callback, SysUiState sysUiState) { mContext = context; mHandler = handler; mSfVsyncFrameProvider = sfVsyncFrameProvider; mWindowMagnifierCallback = callback; + mSysUiState = sysUiState; final Display display = mContext.getDisplay(); mDisplayId = mContext.getDisplayId(); @@ -170,13 +179,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mMirrorViewRunnable = () -> { if (mMirrorView != null) { mMirrorView.getBoundsOnScreen(mMirrorViewBounds); + updateSystemUIStateIfNeeded(); mWindowMagnifierCallback.onWindowMagnifierBoundsChanged( mDisplayId, mMirrorViewBounds); } }; mMirrorViewLayoutChangeListener = - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (!mHandler.hasCallbacks(mMirrorViewRunnable)) { mHandler.post(mMirrorViewRunnable); + } + }; + mMirrorSurfaceViewLayoutChangeListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> applyTapExcludeRegion(); @@ -199,6 +213,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mMirrorView.setStateDescription(formatStateDescription(mScale)); } }; + mWindowInsetChangeRunnable = this::onWindowInsetChanged; } private void updateDimensions() { @@ -210,7 +225,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold R.dimen.magnification_drag_view_size); mOuterBorderSize = mResources.getDimensionPixelSize( R.dimen.magnification_outer_border_margin); - updateNavigationBarDimensions(); } private void computeBounceAnimationScale() { @@ -220,16 +234,16 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mBounceEffectAnimationScale = Math.min(animationScaleMax, ANIMATION_BOUNCE_EFFECT_SCALE); } - private void updateNavigationBarDimensions() { - if (!supportsSwipeUpGesture()) { - mNavGestureHeight = 0; - return; + private boolean updateSystemGestureInsetsTop() { + final WindowMetrics windowMetrics = mWm.getCurrentWindowMetrics(); + final Insets insets = windowMetrics.getWindowInsets().getInsets(systemGestures()); + final int gestureTop = + insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1; + if (gestureTop != mSystemGestureTop) { + mSystemGestureTop = gestureTop; + return true; } - mNavGestureHeight = (mWindowBounds.width() > mWindowBounds.height()) - ? mResources.getDimensionPixelSize( - com.android.internal.R.dimen.navigation_bar_height_landscape) - : mResources.getDimensionPixelSize( - com.android.internal.R.dimen.navigation_bar_gesture_height); + return false; } /** @@ -255,6 +269,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (mMirrorWindowControl != null) { mMirrorWindowControl.destroyControl(); } + updateSystemUIStateIfNeeded(); } /** @@ -277,6 +292,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } } + private void updateSystemUIStateIfNeeded() { + updateSysUIState(false); + } + private void updateAccessibilityWindowTitleIfNeeded() { if (!isWindowVisible()) return; LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams(); @@ -284,13 +303,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mWm.updateViewLayout(mMirrorView, params); } - /** Handles MirrorWindow position when the navigation bar mode changed. */ - public void onNavigationModeChanged(int mode) { - mNavBarMode = mode; - updateNavigationBarDimensions(); - updateMirrorViewLayout(); - } - /** Handles MirrorWindow position when the device rotation changed. */ private void onRotate() { final Display display = mContext.getDisplay(); @@ -299,7 +311,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold setMagnificationFrameBoundary(); mRotation = display.getRotation(); - updateNavigationBarDimensions(); if (!isWindowVisible()) { return; @@ -351,6 +362,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + params.receiveInsetsIgnoringZOrder = true; params.setTitle(mContext.getString(R.string.magnification_window_title)); params.accessibilityTitle = getAccessibilityWindowTitle(); @@ -368,6 +380,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener); mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate()); + mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> { + if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) { + mHandler.post(mWindowInsetChangeRunnable); + } + return v.onApplyWindowInsets(insets); + }); mWm.addView(mMirrorView, params); @@ -377,6 +395,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold addDragTouchListeners(); } + private void onWindowInsetChanged() { + if (updateSystemGestureInsetsTop()) { + updateSystemUIStateIfNeeded(); + } + } + private void applyTapExcludeRegion() { final Region tapExcludeRegion = calculateTapExclude(); final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken()); @@ -464,17 +488,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return; } final int maxMirrorViewX = mWindowBounds.width() - mMirrorView.getWidth(); - final int maxMirrorViewY = - mWindowBounds.height() - mMirrorView.getHeight() - mNavGestureHeight; + final int maxMirrorViewY = mWindowBounds.height() - mMirrorView.getHeight(); + LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams(); params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; - // If nav bar mode supports swipe-up gesture, the Y position of mirror view should not - // overlap nav bar window to prevent window-dragging obscured. - if (supportsSwipeUpGesture()) { - params.y = Math.min(params.y, maxMirrorViewY); - } // Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView // able to move close to the screen edges. @@ -508,6 +527,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return false; } + public void updateSysUIStateFlag() { + updateSysUIState(true); + } + /** * Calculates the desired source bounds. This will be the area under from the center of the * displayFrame, factoring in scale. @@ -569,6 +592,16 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return false; } + private void updateSysUIState(boolean force) { + final boolean overlap = isWindowVisible() && mSystemGestureTop > 0 + && mMirrorViewBounds.bottom > mSystemGestureTop; + if (force || overlap != mOverlapWithGestureInsets) { + mOverlapWithGestureInsets = overlap; + mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, mOverlapWithGestureInsets) + .commitUpdate(mDisplayId); + } + } + @Override public void surfaceCreated(SurfaceHolder holder) { createMirror(); @@ -676,10 +709,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return mMirrorView != null; } - private boolean supportsSwipeUpGesture() { - return mNavBarMode == NAV_BAR_MODE_2BUTTON || mNavBarMode == NAV_BAR_MODE_GESTURAL; - } - private CharSequence formatStateDescription(float scale) { // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed // non-null, so the first time this is called we will always get the appropriate @@ -722,6 +751,14 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold scaleAnimator.start(); } + public void dump(PrintWriter pw) { + pw.println("WindowMagnificationController (displayId=" + mDisplayId + "):"); + pw.println(" mOverlapWithGestureInsets:" + mOverlapWithGestureInsets); + pw.println(" mScale:" + mScale); + pw.println(" mMirrorViewBounds:" + (isWindowVisible() ? mMirrorViewBounds : "empty")); + pw.println(" mSystemGestureTop:" + mSystemGestureTop); + } + private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate { @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java index 3b3bad3b255f..ee6276813512 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java @@ -18,11 +18,13 @@ package com.android.systemui.accessibility.floatingmenu; import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED; import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE; +import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT; import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY; import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; +import static com.android.systemui.Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP; import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType; import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType; @@ -34,15 +36,19 @@ import android.os.UserHandle; import android.provider.Settings; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Prefs; /** * Contains logic for an accessibility floating menu view. */ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { - private static final int DEFAULT_FADE_EFFECT_ENABLED = 1; + private static final int DEFAULT_FADE_EFFECT_IS_ENABLED = 1; + private static final int DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED = 0; private static final float DEFAULT_OPACITY_VALUE = 0.55f; private final Context mContext; private final AccessibilityFloatingMenuView mMenuView; + private final MigrationTooltipView mMigrationTooltipView; + private final DockTooltipView mDockTooltipView; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final ContentObserver mContentObserver = @@ -86,6 +92,8 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { AccessibilityFloatingMenu(Context context, AccessibilityFloatingMenuView menuView) { mContext = context; mMenuView = menuView; + mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView); + mDockTooltipView = new DockTooltipView(mContext, mMenuView); } @Override @@ -105,6 +113,9 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { getOpacityValue(mContext)); mMenuView.setSizeType(getSizeType(mContext)); mMenuView.setShapeType(getShapeType(mContext)); + mMenuView.setOnDragEndListener(this::showDockTooltipIfNecessary); + + showMigrationTooltipIfNecessary(); registerContentObservers(); } @@ -116,14 +127,48 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { } mMenuView.hide(); + mMigrationTooltipView.hide(); + mDockTooltipView.hide(); unregisterContentObservers(); } + // Migration tooltip was the android S feature. It's just used on the Android version from R + // to S. In addition, it only shows once. + private void showMigrationTooltipIfNecessary() { + if (isMigrationTooltipPromptEnabled(mContext)) { + mMigrationTooltipView.show(); + + Settings.Secure.putInt(mContext.getContentResolver(), + ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, /* disabled */ 0); + } + } + + private static boolean isMigrationTooltipPromptEnabled(Context context) { + return Settings.Secure.getInt( + context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, + DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1; + } + + /** + * Shows tooltip when user drags accessibility floating menu for the first time. + */ + private void showDockTooltipIfNecessary() { + if (!Prefs.get(mContext).getBoolean( + HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, false)) { + // if the menu is an oval, the user has already dragged it out, so show the tooltip. + if (mMenuView.isOvalShape()) { + mDockTooltipView.show(); + } + + Prefs.putBoolean(mContext, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true); + } + } + private static boolean isFadeEffectEnabled(Context context) { return Settings.Secure.getInt( context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, - DEFAULT_FADE_EFFECT_ENABLED) == /* enable */ 1; + DEFAULT_FADE_EFFECT_IS_ENABLED) == /* enabled */ 1; } private static float getOpacityValue(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java index 934e20dfbd05..49e6b47b3b79 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java @@ -19,6 +19,8 @@ package com.android.systemui.accessibility.floatingmenu; import static android.util.MathUtils.constrain; import static android.util.MathUtils.sq; +import static java.util.Objects.requireNonNull; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -43,7 +45,9 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.view.animation.Animation; import android.view.animation.OvershootInterpolator; +import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import androidx.annotation.DimenRes; @@ -60,6 +64,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; /** * Accessibility floating menu is used for the actions of accessibility features, it's also the @@ -78,6 +83,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout private static final int MIN_WINDOW_Y = 0; private static final float LOCATION_Y_PERCENTAGE = 0.8f; + private static final int ANIMATION_START_OFFSET = 600; + private static final int ANIMATION_DURATION_MS = 600; + private static final float ANIMATION_TO_X_VALUE = 0.5f; + private boolean mIsFadeEffectEnabled; private boolean mIsShowing; private boolean mIsDownInEnlargedTouchArea; @@ -107,6 +116,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout private float mPercentageY = LOCATION_Y_PERCENTAGE; private float mSquareScaledTouchSlop; private final Configuration mLastConfiguration; + private Optional<OnDragEndListener> mOnDragEndListener = Optional.empty(); private final RecyclerView mListView; private final AccessibilityTargetAdapter mAdapter; private float mFadeOutValue; @@ -161,6 +171,17 @@ public class AccessibilityFloatingMenuView extends FrameLayout int RIGHT = 1; } + /** + * Interface for a callback to be invoked when the floating menu was dragging. + */ + interface OnDragEndListener { + + /** + * Invoked when the floating menu has dragged end. + */ + void onDragEnd(); + } + public AccessibilityFloatingMenuView(Context context) { this(context, new RecyclerView(context)); } @@ -201,6 +222,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout updateRadiusWith(mSizeType, mRadiusType, mTargets.size()); fadeOut(); + + mOnDragEndListener.ifPresent(OnDragEndListener::onDragEnd); } }); @@ -266,7 +289,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout // Must switch the oval shape type before tapping the corresponding item in the // list view, otherwise it can't work on it. - if (mShapeType == ShapeType.HALF_OVAL) { + if (!isOvalShape()) { setShapeType(ShapeType.OVAL); return true; @@ -299,10 +322,6 @@ public class AccessibilityFloatingMenuView extends FrameLayout @Override public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { - return true; - } - fadeIn(); final Rect bounds = getAvailableBounds(); @@ -340,7 +359,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout return true; } - return false; + return super.performAccessibilityAction(action, arguments); } void show() { @@ -367,6 +386,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout return mIsShowing; } + boolean isOvalShape() { + return mShapeType == ShapeType.OVAL; + } + void onTargetsChanged(List<AccessibilityTarget> newTargets) { fadeIn(); @@ -411,6 +434,41 @@ public class AccessibilityFloatingMenuView extends FrameLayout fadeOut(); } + public void setOnDragEndListener(OnDragEndListener onDragListener) { + mOnDragEndListener = Optional.ofNullable(onDragListener); + } + + void startTranslateXAnimation() { + fadeIn(); + + final float toXValue = mAlignment == Alignment.RIGHT + ? ANIMATION_TO_X_VALUE + : -ANIMATION_TO_X_VALUE; + final TranslateAnimation animation = + new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, toXValue, + Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, 0); + animation.setDuration(ANIMATION_DURATION_MS); + animation.setRepeatMode(Animation.REVERSE); + animation.setInterpolator(new OvershootInterpolator()); + animation.setRepeatCount(Animation.INFINITE); + animation.setStartOffset(ANIMATION_START_OFFSET); + mListView.startAnimation(animation); + } + + void stopTranslateXAnimation() { + mListView.clearAnimation(); + + fadeOut(); + } + + Rect getWindowLocationOnScreen() { + final int left = mCurrentLayoutParams.x; + final int top = mCurrentLayoutParams.y; + return new Rect(left, top, left + getWindowWidth(), top + getWindowHeight()); + } + void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) { mIsFadeEffectEnabled = isFadeEffectEnabled; mFadeOutValue = newOpacityValue; @@ -534,11 +592,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout } private Handler createUiHandler() { - final Looper looper = Looper.myLooper(); - if (looper == null) { - throw new IllegalArgumentException("looper must not be null"); - } - return new Handler(looper); + return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null")); } private void updateDimensions() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java new file mode 100644 index 000000000000..d8e80fe99331 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java @@ -0,0 +1,91 @@ +/* + * 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.accessibility.floatingmenu; + +import android.text.Annotation; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.ClickableSpan; +import android.view.View; + +import androidx.annotation.NonNull; + +import java.util.Arrays; +import java.util.Optional; + +/** + * A span that turns the text wrapped by annotation tag into the clickable link text. + */ +class AnnotationLinkSpan extends ClickableSpan { + private final Optional<View.OnClickListener> mClickListener; + + private AnnotationLinkSpan(View.OnClickListener listener) { + mClickListener = Optional.ofNullable(listener); + } + + @Override + public void onClick(View view) { + mClickListener.ifPresent(listener -> listener.onClick(view)); + } + + /** + * Makes the text has the link with the click action. In addition, the span will match first + * LinkInfo and attach into the text. + * + * @param text the text wrapped by annotation tag + * @param linkInfos used to attach the click action into the corresponding span + * @return the text attached with the span + */ + static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) { + final SpannableString msg = new SpannableString(text); + final Annotation[] spans = + msg.getSpans(/* queryStart= */ 0, msg.length(), Annotation.class); + final SpannableStringBuilder builder = new SpannableStringBuilder(msg); + + Arrays.asList(spans).forEach(annotationTag -> { + final String key = annotationTag.getValue(); + final Optional<LinkInfo> linkInfo = + Arrays.asList(linkInfos).stream().filter( + info -> info.mAnnotation.isPresent() + && info.mAnnotation.get().equals(key)).findFirst(); + + linkInfo.flatMap(info -> info.mListener).ifPresent(listener -> { + final AnnotationLinkSpan span = new AnnotationLinkSpan(listener); + builder.setSpan(span, + msg.getSpanStart(annotationTag), + msg.getSpanEnd(annotationTag), + msg.getSpanFlags(span)); + }); + }); + + return builder; + } + + /** + * Data class to store the annotation and the click action. + */ + static class LinkInfo { + static final String DEFAULT_ANNOTATION = "link"; + private final Optional<String> mAnnotation; + private final Optional<View.OnClickListener> mListener; + + LinkInfo(@NonNull String annotation, View.OnClickListener listener) { + mAnnotation = Optional.of(annotation); + mListener = Optional.ofNullable(listener); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java new file mode 100644 index 000000000000..1abf559d6846 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java @@ -0,0 +1,297 @@ +/* + * 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.accessibility.floatingmenu; + +import static android.util.TypedValue.COMPLEX_UNIT_PX; +import static android.view.View.MeasureSpec.AT_MOST; +import static android.view.View.MeasureSpec.UNSPECIFIED; + +import android.annotation.UiContext; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.os.Bundle; +import android.text.method.MovementMethod; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.settingslib.Utils; +import com.android.systemui.R; +import com.android.systemui.recents.TriangleShape; + +/** + * Base tooltip view that shows the information about the operation of the + * Accessibility floating menu. In addition, the anchor view is only for {@link + * AccessibilityFloatingMenuView}, it should be more suited for displaying one-off menus to avoid + * the performance hit for the extra window. + */ +class BaseTooltipView extends FrameLayout { + private int mFontSize; + private int mTextViewMargin; + private int mTextViewPadding; + private int mTextViewCornerRadius; + private int mArrowMargin; + private int mArrowWidth; + private int mArrowHeight; + private int mArrowCornerRadius; + private int mScreenWidth; + private boolean mIsShowing; + private TextView mTextView; + private final WindowManager.LayoutParams mCurrentLayoutParams; + private final WindowManager mWindowManager; + private final AccessibilityFloatingMenuView mAnchorView; + + BaseTooltipView(@UiContext Context context, AccessibilityFloatingMenuView anchorView) { + super(context); + mWindowManager = context.getSystemService(WindowManager.class); + mAnchorView = anchorView; + mCurrentLayoutParams = createDefaultLayoutParams(); + + updateDimensions(); + initViews(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + updateDimensions(); + updateTextView(); + + mAnchorView.onConfigurationChanged(newConfig); + final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen(); + updateArrowWith(anchorViewLocation); + updateWidthWith(anchorViewLocation); + updateLocationWith(anchorViewLocation); + + mWindowManager.updateViewLayout(this, mCurrentLayoutParams); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + hide(); + } + + return super.onTouchEvent(event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + + info.addAction(AccessibilityAction.ACTION_DISMISS); + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (action == AccessibilityAction.ACTION_DISMISS.getId()) { + hide(); + return true; + } + + return super.performAccessibilityAction(action, arguments); + } + + void show() { + if (isShowing()) { + return; + } + + mIsShowing = true; + final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen(); + updateArrowWith(anchorViewLocation); + updateWidthWith(anchorViewLocation); + updateLocationWith(anchorViewLocation); + + mWindowManager.addView(this, mCurrentLayoutParams); + } + + void hide() { + if (!isShowing()) { + return; + } + + mIsShowing = false; + mWindowManager.removeView(this); + } + + void setDescription(CharSequence text) { + mTextView.setText(text); + } + + void setMovementMethod(MovementMethod movement) { + mTextView.setMovementMethod(movement); + } + + private boolean isShowing() { + return mIsShowing; + } + + private void initViews() { + final View contentView = + LayoutInflater.from(getContext()).inflate( + R.layout.accessibility_floating_menu_tooltip, this, false); + + mTextView = contentView.findViewById(R.id.text); + + addView(contentView); + } + + private static WindowManager.LayoutParams createDefaultLayoutParams() { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, + PixelFormat.TRANSLUCENT); + params.windowAnimations = android.R.style.Animation_Translucent; + params.gravity = Gravity.START | Gravity.TOP; + + return params; + } + + private void updateDimensions() { + final Resources res = getResources(); + final DisplayMetrics dm = res.getDisplayMetrics(); + mScreenWidth = dm.widthPixels; + mArrowWidth = + res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width); + mArrowHeight = + res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_height); + mArrowMargin = + res.getDimensionPixelSize( + R.dimen.accessibility_floating_tooltip_arrow_margin); + mArrowCornerRadius = + res.getDimensionPixelSize( + R.dimen.accessibility_floating_tooltip_arrow_corner_radius); + mFontSize = + res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_font_size); + mTextViewMargin = + res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_margin); + mTextViewPadding = + res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_padding); + mTextViewCornerRadius = + res.getDimensionPixelSize( + R.dimen.accessibility_floating_tooltip_text_corner_radius); + } + + private void updateTextView() { + mTextView.setTextSize(COMPLEX_UNIT_PX, mFontSize); + mTextView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding, + mTextViewPadding); + + final GradientDrawable gradientDrawable = (GradientDrawable) mTextView.getBackground(); + gradientDrawable.setCornerRadius(mTextViewCornerRadius); + } + + private void updateArrowWith(Rect anchorViewLocation) { + final boolean isAnchorViewOnLeft = isAnchorViewOnLeft(anchorViewLocation); + final View arrowView = findViewById(isAnchorViewOnLeft + ? R.id.arrow_left + : R.id.arrow_right); + arrowView.setVisibility(VISIBLE); + drawArrow(arrowView, isAnchorViewOnLeft); + + final LinearLayout.LayoutParams layoutParams = + (LinearLayout.LayoutParams) arrowView.getLayoutParams(); + layoutParams.width = mArrowWidth; + layoutParams.height = mArrowHeight; + + final int leftMargin = isAnchorViewOnLeft ? 0 : mArrowMargin; + final int rightMargin = isAnchorViewOnLeft ? mArrowMargin : 0; + layoutParams.setMargins(leftMargin, 0, rightMargin, 0); + arrowView.setLayoutParams(layoutParams); + } + + private void updateWidthWith(Rect anchorViewLocation) { + final ViewGroup.LayoutParams layoutParams = mTextView.getLayoutParams(); + layoutParams.width = getTextWidthWith(anchorViewLocation); + mTextView.setLayoutParams(layoutParams); + } + + private void updateLocationWith(Rect anchorViewLocation) { + mCurrentLayoutParams.x = isAnchorViewOnLeft(anchorViewLocation) + ? anchorViewLocation.width() + : mScreenWidth - getWindowWidthWith(anchorViewLocation) + - anchorViewLocation.width(); + mCurrentLayoutParams.y = + anchorViewLocation.centerY() - (getTextHeightWith(anchorViewLocation) / 2); + } + + private void drawArrow(View view, boolean isPointingLeft) { + final ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + final TriangleShape triangleShape = + TriangleShape.createHorizontal(layoutParams.width, layoutParams.height, + isPointingLeft); + final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape); + final Paint arrowPaint = arrowDrawable.getPaint(); + arrowPaint.setColor(Utils.getColorAttrDefaultColor(getContext(), + com.android.internal.R.attr.colorAccentPrimary)); + final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius); + arrowPaint.setPathEffect(effect); + view.setBackground(arrowDrawable); + } + + private boolean isAnchorViewOnLeft(Rect anchorViewLocation) { + return anchorViewLocation.left < (mScreenWidth / 2); + } + + private int getTextWidthWith(Rect anchorViewLocation) { + final int widthSpec = + MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST); + final int heightSpec = + MeasureSpec.makeMeasureSpec(0, UNSPECIFIED); + mTextView.measure(widthSpec, heightSpec); + return mTextView.getMeasuredWidth(); + } + + private int getTextHeightWith(Rect anchorViewLocation) { + final int widthSpec = + MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST); + final int heightSpec = + MeasureSpec.makeMeasureSpec(0, UNSPECIFIED); + mTextView.measure(widthSpec, heightSpec); + return mTextView.getMeasuredHeight(); + } + + private int getAvailableTextWidthWith(Rect anchorViewLocation) { + return mScreenWidth - anchorViewLocation.width() - mArrowWidth - mArrowMargin + - mTextViewMargin; + } + + private int getWindowWidthWith(Rect anchorViewLocation) { + return getTextWidthWith(anchorViewLocation) + mArrowWidth + mArrowMargin; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java new file mode 100644 index 000000000000..49056a6039ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java @@ -0,0 +1,50 @@ +/* + * 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.accessibility.floatingmenu; + +import android.content.Context; + +import com.android.systemui.R; + +/** + * Dock tooltip view that shows the info about moving the Accessibility button to the edge to hide. + */ +class DockTooltipView extends BaseTooltipView { + private final AccessibilityFloatingMenuView mAnchorView; + + DockTooltipView(Context context, AccessibilityFloatingMenuView anchorView) { + super(context, anchorView); + mAnchorView = anchorView; + + setDescription( + getContext().getText(R.string.accessibility_floating_button_docking_tooltip)); + } + + @Override + void hide() { + super.hide(); + + mAnchorView.stopTranslateXAnimation(); + } + + @Override + void show() { + super.show(); + + mAnchorView.startTranslateXAnimation(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java new file mode 100644 index 000000000000..e4f3e3145998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.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 com.android.systemui.accessibility.floatingmenu; + +import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.text.method.LinkMovementMethod; + +import com.android.systemui.R; + +/** + * Migration tooltip view that shows the information about the Accessibility button was replaced + * with the floating menu. + */ +class MigrationTooltipView extends BaseTooltipView { + MigrationTooltipView(Context context, AccessibilityFloatingMenuView anchorView) { + super(context, anchorView); + + final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Intent.EXTRA_COMPONENT_NAME, + ACCESSIBILITY_BUTTON_COMPONENT_NAME.flattenToShortString()); + + final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo( + AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION, + v -> { + getContext().startActivity(intent); + hide(); + }); + + final int textResId = R.string.accessibility_floating_button_migration_tooltip; + setDescription(AnnotationLinkSpan.linkify(getContext().getText(textResId), linkInfo)); + setMovementMethod(LinkMovementMethod.getInstance()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index e243e1e76c54..875bfdbffff3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -59,7 +59,6 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; -import com.android.systemui.biometrics.HbmTypes.HbmType; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; @@ -72,6 +71,8 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.util.concurrency.DelayableExecutor; +import java.util.Optional; + import javax.inject.Inject; /** @@ -87,7 +88,7 @@ import javax.inject.Inject; */ @SuppressWarnings("deprecation") @SysUISingleton -public class UdfpsController implements DozeReceiver, HbmCallback { +public class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; @@ -105,11 +106,12 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull private final DumpManager mDumpManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardViewMediator mKeyguardViewMediator; - @NonNull private final Vibrator mVibrator; + @Nullable private final Vibrator mVibrator; @NonNull private final Handler mMainHandler; @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; + @Nullable private final UdfpsHbmCallback mHbmCallback; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; @@ -135,7 +137,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private boolean mScreenOn; private Runnable mAodInterruptRunnable; - private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = + @VisibleForTesting + static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) @@ -144,7 +147,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private final VibrationEffect mEffectTick = VibrationEffect.get(VibrationEffect.EFFECT_TICK); private final VibrationEffect mEffectTextureTick = VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK); - private final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + @VisibleForTesting + final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); private final VibrationEffect mEffectHeavy = VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); private final VibrationEffect mDoubleClick = @@ -152,6 +156,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private final Runnable mAcquiredVibration = new Runnable() { @Override public void run() { + if (mVibrator == null) { + return; + } String effect = Settings.Global.getString(mContext.getContentResolver(), "udfps_acquired_type"); mVibrator.vibrate(getVibration(effect, mEffectTick), VIBRATION_SONIFICATION_ATTRIBUTES); @@ -345,7 +352,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } if (isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView)) { Trace.beginAsyncSection( - "UdfpsController.mOnTouchListener#isWithinSensorArea", 1); + "UdfpsController#ACTION_DOWN", 1); // The pointer that causes ACTION_DOWN is always at index 0. // We need to persist its ID to track it during ACTION_MOVE that could include // data for many other pointers because of multi-touch support. @@ -382,8 +389,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { minor, major, v, exceedsVelocityThreshold); final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime; if (!isFingerDown && !exceedsVelocityThreshold) { - Trace.endAsyncSection( - "UdfpsController.mOnTouchListener#isWithinSensorArea", 1); onFingerDown((int) x, (int) y, minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = SystemClock.elapsedRealtime(); @@ -391,24 +396,28 @@ public class UdfpsController implements DozeReceiver, HbmCallback { PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); // TODO: this should eventually be removed after ux testing - final ContentResolver contentResolver = mContext.getContentResolver(); - int startEnabled = Settings.Global.getInt(contentResolver, - "udfps_start", 0); - if (startEnabled > 0) { - String startEffectSetting = Settings.Global.getString( - contentResolver, "udfps_start_type"); - mVibrator.vibrate(getVibration(startEffectSetting, mEffectClick), - VIBRATION_SONIFICATION_ATTRIBUTES); + if (mVibrator != null) { + final ContentResolver contentResolver = + mContext.getContentResolver(); + int startEnabled = Settings.Global.getInt(contentResolver, + "udfps_start", 1); + if (startEnabled > 0) { + String startEffectSetting = Settings.Global.getString( + contentResolver, "udfps_start_type"); + mVibrator.vibrate(getVibration(startEffectSetting, + mEffectClick), VIBRATION_SONIFICATION_ATTRIBUTES); + } + + int acquiredEnabled = Settings.Global.getInt(contentResolver, + "udfps_acquired", 0); + if (acquiredEnabled > 0) { + int delay = Settings.Global.getInt(contentResolver, + "udfps_acquired_delay", 500); + mMainHandler.removeCallbacks(mAcquiredVibration); + mMainHandler.postDelayed(mAcquiredVibration, delay); + } } - int acquiredEnabled = Settings.Global.getInt(contentResolver, - "udfps_acquired", 0); - if (acquiredEnabled > 0) { - int delay = Settings.Global.getInt(contentResolver, - "udfps_acquired_delay", 500); - mMainHandler.removeCallbacks(mAcquiredVibration); - mMainHandler.postDelayed(mAcquiredVibration, delay); - } handled = true; } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { Log.v(TAG, "onTouch | finger move: " + touchInfo); @@ -458,11 +467,13 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, - @NonNull ScreenLifecycle screenLifecycle) { + @NonNull ScreenLifecycle screenLifecycle, + @Nullable Vibrator vibrator, + @NonNull Optional<UdfpsHbmCallback> hbmCallback) { mContext = context; // TODO (b/185124905): inject main handler and vibrator once done prototyping mMainHandler = new Handler(Looper.getMainLooper()); - mVibrator = context.getSystemService(Vibrator.class); + mVibrator = vibrator; mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the // fingerprint manager should never be null. @@ -478,6 +489,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mFalsingManager = falsingManager; mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; + mHbmCallback = hbmCallback.orElse(null); screenLifecycle.addObserver(mScreenObserver); mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON; @@ -611,7 +623,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason); mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false); mView.setSensorProperties(mSensorProps); - mView.setHbmCallback(this); + mView.setHbmCallback(mHbmCallback); UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason); animation.init(); mView.setAnimationViewController(animation); @@ -761,10 +773,13 @@ public class UdfpsController implements DozeReceiver, HbmCallback { Log.w(TAG, "Null view in onFingerDown"); return; } + mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); + Trace.endAsyncSection( + "UdfpsController#ACTION_DOWN", 1); Trace.beginAsyncSection("UdfpsController#startIllumination", 1); mView.startIllumination(() -> { + mFingerprintManager.onUiReady(mSensorProps.sensorId); Trace.endAsyncSection("UdfpsController#startIllumination", 1); - mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); }); } @@ -780,17 +795,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mView.stopIllumination(); } - @Override - public void enableHbm(@HbmType int hbmType, @Nullable Surface surface) { - // Do nothing. This method can be implemented for devices that require the high-brightness - // mode for fingerprint illumination. - } - - @Override - public void disableHbm(@HbmType int hbmType, @Nullable Surface surface) { - // Do nothing. This method can be implemented for devices that require the high-brightness - // mode for fingerprint illumination. - } private VibrationEffect getVibration(String effect, VibrationEffect defaultEffect) { if (TextUtils.isEmpty(effect)) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index cd5abd74c260..2c48d0ad8b18 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -36,7 +36,7 @@ import com.android.systemui.R; public class UdfpsEnrollDrawable extends UdfpsDrawable { private static final String TAG = "UdfpsAnimationEnroll"; - static final float PROGRESS_BAR_RADIUS = 180.f; + static final float PROGRESS_BAR_RADIUS = 360.f; @NonNull private final Drawable mMovingTargetFpIcon; @NonNull private final Paint mSensorOutlinePaint; @@ -96,11 +96,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { return; } - if (mSensorRect != null) { - canvas.drawOval(mSensorRect, mSensorOutlinePaint); - } - mFingerprintDrawable.draw(canvas); - // Draw moving target if (mEnrollHelper.isCenterEnrollmentComplete()) { mFingerprintDrawable.setAlpha(mAlpha == 255 ? 64 : mAlpha); @@ -117,6 +112,10 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mMovingTargetFpIcon.draw(canvas); canvas.restore(); } else { + if (mSensorRect != null) { + canvas.drawOval(mSensorRect, mSensorOutlinePaint); + } + mFingerprintDrawable.draw(canvas); mFingerprintDrawable.setAlpha(mAlpha); mSensorOutlinePaint.setAlpha(mAlpha); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmCallback.java index d90d0f8d9f67..85f0d278e3f1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmCallback.java @@ -19,18 +19,18 @@ package com.android.systemui.biometrics; import android.annotation.Nullable; import android.view.Surface; -import com.android.systemui.biometrics.HbmTypes.HbmType; +import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType; /** * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the * illumination is no longer necessary. */ -public interface HbmCallback { +public interface UdfpsHbmCallback { /** * UdfpsView will call this to enable the HBM when the fingerprint illumination is needed. * - * @param hbmType The type of HBM that should be enabled. See {@link HbmTypes}. + * @param hbmType The type of HBM that should be enabled. See {@link UdfpsHbmTypes}. * @param surface The surface for which the HBM is requested, in case the HBM implementation * needs to set special surface flags to enable the HBM. Can be null. */ @@ -39,7 +39,7 @@ public interface HbmCallback { /** * UdfpsView will call this to disable the HBM when the illumination is not longer needed. * - * @param hbmType The type of HBM that should be disabled. See {@link HbmTypes}. + * @param hbmType The type of HBM that should be disabled. See {@link UdfpsHbmTypes}. * @param surface The surface for which the HBM is requested, in case the HBM implementation * needs to unset special surface flags to disable the HBM. Can be null. */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java index f798005daabb..3ab0bd62eec8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java @@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy; /** * Different high-brightness mode (HBM) types that are relevant to this package. */ -public final class HbmTypes { +public final class UdfpsHbmTypes { /** HBM that applies to the whole screen. */ public static final int GLOBAL_HBM = IUdfpsHbmListener.GLOBAL_HBM; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java index 8bea05b2b54f..1676bcd57afc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java @@ -26,7 +26,7 @@ interface UdfpsIlluminator { /** * @param callback Invoked when HBM should be enabled or disabled. */ - void setHbmCallback(@Nullable HbmCallback callback); + void setHbmCallback(@Nullable UdfpsHbmCallback callback); /** * Invoked when illumination should start. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java index 4d441bd288fe..aa5f0f6cf7f4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java @@ -31,7 +31,7 @@ import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; -import com.android.systemui.biometrics.HbmTypes.HbmType; +import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType; /** * Under-display fingerprint sensor Surface View. The surface should be used for HBM-specific things @@ -41,7 +41,7 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { private static final String TAG = "UdfpsSurfaceView"; private static final String SETTING_HBM_TYPE = "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType"; - private static final @HbmType int DEFAULT_HBM_TYPE = HbmTypes.GLOBAL_HBM; + private static final @HbmType int DEFAULT_HBM_TYPE = UdfpsHbmTypes.GLOBAL_HBM; /** * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has @@ -57,7 +57,7 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { private final @HbmType int mHbmType; @NonNull private RectF mSensorRect; - @Nullable private HbmCallback mHbmCallback; + @Nullable private UdfpsHbmCallback mHbmCallback; public UdfpsSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); @@ -90,7 +90,7 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { } @Override - public void setHbmCallback(@Nullable HbmCallback callback) { + public void setHbmCallback(@Nullable UdfpsHbmCallback callback) { mHbmCallback = callback; } @@ -102,7 +102,7 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { Log.e(TAG, "startIllumination | mHbmCallback is null"); } - if (mHbmType == HbmTypes.GLOBAL_HBM) { + if (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) { drawImmediately(mIlluminationDotDrawable); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index f10d5f3bca1f..a1d30403e0b8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -101,7 +101,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin } @Override - public void setHbmCallback(@Nullable HbmCallback callback) { + public void setHbmCallback(@Nullable UdfpsHbmCallback callback) { mHbmSurfaceView.setHbmCallback(callback); } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java index 9d47bbb03380..f01ac68ed5a2 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java @@ -34,7 +34,7 @@ import android.view.WindowManager; */ public class WirelessChargingAnimation { - public static final long DURATION = 1133; + public static final long DURATION = 1500; private static final String TAG = "WirelessChargingView"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 38ffec115fba..0d3e2ae9a776 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -20,6 +20,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Color; import android.graphics.PointF; import android.util.AttributeSet; import android.util.TypedValue; @@ -42,7 +43,9 @@ import java.text.NumberFormat; */ public class WirelessChargingLayout extends FrameLayout { public static final int UNKNOWN_BATTERY_LEVEL = -1; - private static final long RIPPLE_ANIMATION_DURATION = 1133; + private static final long RIPPLE_ANIMATION_DURATION = 1500; + private static final int SCRIM_COLOR = 0x4C000000; + private static final int SCRIM_FADE_DURATION = 300; private ChargingRippleView mRippleView; public WirelessChargingLayout(Context context) { @@ -121,6 +124,19 @@ public class WirelessChargingLayout extends FrameLayout { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); + ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); + scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); + ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); + scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); + scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION); + AnimatorSet animatorSetScrim = new AnimatorSet(); + animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); + animatorSetScrim.start(); + mRippleView = findViewById(R.id.wireless_charging_ripple); OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index fd80d50c2894..26db33d6dea8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -29,6 +29,7 @@ import android.app.StatsManager; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.app.role.RoleManager; +import android.app.smartspace.SmartspaceManager; import android.app.trust.TrustManager; import android.content.ContentResolver; import android.content.Context; @@ -400,4 +401,10 @@ public class FrameworkServicesModule { static PermissionManager providePermissionManager(Context context) { return context.getSystemService(PermissionManager.class); } + + @Provides + @Singleton + static SmartspaceManager provideSmartspaceManager(Context context) { + return context.getSystemService(SmartspaceManager.class); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7fa48d405643..1396099db948 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -29,6 +29,7 @@ import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.SystemUIFactory; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; +import com.android.systemui.biometrics.UdfpsHbmCallback; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.controls.dagger.ControlsModule; import com.android.systemui.dagger.qualifiers.Main; @@ -160,6 +161,9 @@ public abstract class SystemUIModule { @BindsOptionalOf abstract StatusBar optionalStatusBar(); + @BindsOptionalOf + abstract UdfpsHbmCallback optionalUdfpsHbmCallback(); + @SysUISingleton @Binds abstract SystemClock bindSystemClock(SystemClockImpl systemClock); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index a83b13c50671..3873c25f32c6 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -226,7 +226,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter, this::getWalletViewController, mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter); + mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger()); if (shouldShowLockMessage(dialog)) { dialog.showLockMessage(); @@ -294,11 +294,11 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, - MyPowerOptionsAdapter powerAdapter) { + MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions, adapter, overflowAdapter, depthController, sysuiColorExtractor, statusBarService, notificationShadeWindowController, sysuiState, - onRotateCallback, keyguardShowing, powerAdapter); + onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger); mWalletFactory = walletFactory; // Update window attributes diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 2eb37624f598..f9bb35fcb723 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -249,7 +249,43 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene GA_SCREENSHOT_PRESS(347), @UiEvent(doc = "The global actions screenshot button was long pressed.") - GA_SCREENSHOT_LONG_PRESS(348); + GA_SCREENSHOT_LONG_PRESS(348), + + @UiEvent(doc = "The global actions power off button was pressed.") + GA_SHUTDOWN_PRESS(802), + + @UiEvent(doc = "The global actions power off button was long pressed.") + GA_SHUTDOWN_LONG_PRESS(803), + + @UiEvent(doc = "The global actions reboot button was pressed.") + GA_REBOOT_PRESS(349), + + @UiEvent(doc = "The global actions reboot button was long pressed.") + GA_REBOOT_LONG_PRESS(804), + + @UiEvent(doc = "The global actions lockdown button was pressed.") + GA_LOCKDOWN_PRESS(354), // already created by cwren apparently + + @UiEvent(doc = "Power menu was opened via quick settings button.") + GA_OPEN_QS(805), + + @UiEvent(doc = "Power menu was opened via power + volume up.") + GA_OPEN_POWER_VOLUP(806), + + @UiEvent(doc = "Power menu was opened via long press on power.") + GA_OPEN_LONG_PRESS_POWER(807), + + @UiEvent(doc = "Power menu was closed via long press on power.") + GA_CLOSE_LONG_PRESS_POWER(808), + + @UiEvent(doc = "Power menu was dismissed by back gesture.") + GA_CLOSE_BACK(809), + + @UiEvent(doc = "Power menu was dismissed by tapping outside dialog.") + GA_CLOSE_TAP_OUTSIDE(810), + + @UiEvent(doc = "Power menu was closed via power + volume up.") + GA_CLOSE_POWER_VOLUP(811); private final int mId; @@ -349,6 +385,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return mContext; } + protected UiEventLogger getEventLogger() { + return mUiEventLogger; + } + /** * Show the global actions dialog (creating if necessary) * @@ -581,7 +621,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mAdapter, mOverflowAdapter, mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter); + mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); @@ -679,13 +719,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @VisibleForTesting final class ShutDownAction extends SinglePressAction implements LongPressAction { - private ShutDownAction() { + ShutDownAction() { super(R.drawable.ic_lock_power_off, R.string.global_action_power_off); } @Override public boolean onLongPress() { + mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS); if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.reboot(true); return true; @@ -705,6 +746,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void onPress() { + mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS); // shutdown by making sure radio and power are handled accordingly. mWindowManagerFuncs.shutdown(); } @@ -807,12 +849,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @VisibleForTesting final class RestartAction extends SinglePressAction implements LongPressAction { - private RestartAction() { + RestartAction() { super(R.drawable.ic_restart, R.string.global_action_restart); } @Override public boolean onLongPress() { + mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_LONG_PRESS); if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.reboot(true); return true; @@ -832,6 +875,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void onPress() { + mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_PRESS); mWindowManagerFuncs.reboot(false); } } @@ -1062,6 +1106,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene public void onPress() { mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, UserHandle.USER_ALL); + mUiEventLogger.log(GlobalActionsEvent.GA_LOCKDOWN_PRESS); try { mIWindowManager.lockNow(null); // Lock profiles (if any) on the background thread. @@ -2049,6 +2094,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private ListPopupWindow mOverflowPopup; private Dialog mPowerOptionsDialog; protected final Runnable mOnRotateCallback; + private UiEventLogger mUiEventLogger; protected ViewGroup mContainer; @@ -2058,7 +2104,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, - MyPowerOptionsAdapter powerAdapter) { + MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger) { super(context, themeRes); mContext = context; mAdapter = adapter; @@ -2071,6 +2117,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mSysUiState = sysuiState; mOnRotateCallback = onRotateCallback; mKeyguardShowing = keyguardShowing; + mUiEventLogger = uiEventLogger; // Window initialization Window window = getWindow(); @@ -2141,7 +2188,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mGlobalActionsLayout.setAdapter(mAdapter); mContainer = findViewById(com.android.systemui.R.id.global_actions_container); mContainer.setOnClickListener(v -> { - // TODO: add logging (b/182830510) + mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); cancel(); }); @@ -2218,6 +2265,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @Override + public void onBackPressed() { + super.onBackPressed(); + mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_BACK); + } + + @Override public void show() { super.show(); // split this up so we can override but still call Dialog.show diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c8028d3e4b74..a17a4e5f0be0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -105,6 +105,7 @@ import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -230,6 +231,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private AlarmManager mAlarmManager; private AudioManager mAudioManager; private StatusBarManager mStatusBarManager; + private final SysuiStatusBarStateController mStatusBarStateController; private final Executor mUiBgExecutor; private boolean mSystemReady; @@ -794,7 +796,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, - StatusBarStateController statusBarStateController, + SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy) { super(context); @@ -823,6 +825,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode); })); mDozeParameters = dozeParameters; + mStatusBarStateController = statusBarStateController; statusBarStateController.addCallback(this); mKeyguardStateController = keyguardStateController; @@ -2119,19 +2122,17 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, playSounds(false); } - if (KeyguardService.sEnableRemoteKeyguardAnimation) { + // When remaining on the shade, there's no need to do a fancy remote animation, + // it will dismiss the panel in that case. + if (KeyguardService.sEnableRemoteKeyguardAnimation + && !mStatusBarStateController.leaveOpenOnKeyguardHide() + && apps != null && apps.length > 0) { mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback; mSurfaceBehindRemoteAnimationRunning = true; - if (apps != null && apps.length > 0) { - // Pass the surface and metadata to the unlock animation controller. - mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation( - apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); - } else { - // We weren't given any surfaces to animate, so just finish. - onKeyguardExitRemoteAnimationFinished(); - return; - } + // Pass the surface and metadata to the unlock animation controller. + mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation( + apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); } else { setShowingLocked(false); mWakeAndUnlocking = false; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index ecee1b508ce0..119e9c433f67 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -47,6 +47,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; @@ -94,7 +95,7 @@ public class KeyguardModule { NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, - StatusBarStateController statusBarStateController, + SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController) { return new KeyguardViewMediator( diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 644876c6b1af..ef53233c22d4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -115,7 +115,7 @@ class MediaCarouselController @Inject constructor( private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() private var bgColor = getBackgroundColor() - private var shouldScrollToActivePlayer: Boolean = false + protected var shouldScrollToActivePlayer: Boolean = false private var isRtl: Boolean = false set(value) { if (value != field) { @@ -184,7 +184,14 @@ class MediaCarouselController @Inject constructor( true /* persistent */) mediaManager.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - addOrUpdatePlayer(key, oldKey, data) + if (addOrUpdatePlayer(key, oldKey, data)) { + MediaPlayerData.getMediaPlayer(key, null)?.let { + logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + it.mInstanceId, + /* isRecommendationCard */ false, + it.surfaceForSmartspaceLogging) + } + } val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active if (canRemove && !Utils.useMediaResumption(context)) { // This view isn't playing, let's remove this! This happens e.g when @@ -203,6 +210,12 @@ class MediaCarouselController @Inject constructor( override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) { Log.d(TAG, "My Smartspace media update is here") addSmartspaceMediaRecommendations(key, data) + MediaPlayerData.getMediaPlayer(key, null)?.let { + logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + it.mInstanceId, + /* isRecommendationCard */ true, + it.surfaceForSmartspaceLogging) + } if (mediaCarouselScrollHandler.visibleToUser) { logSmartspaceImpression() } @@ -276,7 +289,8 @@ class MediaCarouselController @Inject constructor( } } - private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) { + // Returns true if new player is added + private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData): Boolean { val dataCopy = data.copy(backgroundColor = bgColor) val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey) if (existingPlayer == null) { @@ -295,7 +309,7 @@ class MediaCarouselController @Inject constructor( } else { existingPlayer.bindPlayer(dataCopy, key) MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer) - if (visualStabilityManager.isReorderingAllowed) { + if (visualStabilityManager.isReorderingAllowed || shouldScrollToActivePlayer) { reorderAllPlayers() } else { needsReordering = true @@ -309,6 +323,7 @@ class MediaCarouselController @Inject constructor( if (MediaPlayerData.players().size != mediaContent.childCount) { Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync") } + return existingPlayer == null } private fun addSmartspaceMediaRecommendations(key: String, data: SmartspaceTarget) { @@ -325,7 +340,7 @@ class MediaCarouselController @Inject constructor( val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) - newRecs.bindRecommendation(data, bgColor, { v -> shouldScrollToActivePlayer = true }) + newRecs.bindRecommendation(data, bgColor) MediaPlayerData.addMediaRecommendation(key, newRecs) updatePlayerToState(newRecs, noAnimation = true) reorderAllPlayers() @@ -520,12 +535,20 @@ class MediaCarouselController @Inject constructor( this.desiredLocation = desiredLocation this.desiredHostState = it currentlyExpanded = it.expansion > 0 + + val shouldCloseGuts = !currentlyExpanded && !mediaManager.hasActiveMedia() && + desiredHostState.showsOnlyActiveMedia + for (mediaPlayer in MediaPlayerData.players()) { if (animate) { mediaPlayer.mediaViewController.animatePendingStateChange( duration = duration, delay = startDelay) } + if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) { + mediaPlayer.closeGuts(!animate) + } + mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation) } mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia @@ -541,9 +564,9 @@ class MediaCarouselController @Inject constructor( } } - fun closeGuts() { + fun closeGuts(immediate: Boolean = true) { MediaPlayerData.players().forEach { - it.closeGuts(true) + it.closeGuts(immediate) } } @@ -641,7 +664,7 @@ class MediaCarouselController @Inject constructor( @VisibleForTesting internal object MediaPlayerData { private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null, - emptyList(), emptyList(), "INVALID", null, null, null, false, null) + emptyList(), emptyList(), "INVALID", null, null, null, true, null) data class MediaSortKey( // Is Smartspace media recommendation. When the Smartspace media is present, it should @@ -694,7 +717,7 @@ internal object MediaPlayerData { /** Returns the index of the first non-timeout media. */ fun getActiveMediaIndex(): Int { mediaPlayers.entries.forEachIndexed { index, e -> - if (e.key.data.active) { + if (!e.key.isSsMediaRec && e.key.data.active) { return index } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt index e5a62711a8d0..c806bcfed345 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt @@ -59,7 +59,7 @@ class MediaCarouselScrollHandler( private val mainExecutor: DelayableExecutor, private val dismissCallback: () -> Unit, private var translationChangedListener: () -> Unit, - private val closeGuts: () -> Unit, + private val closeGuts: (immediate: Boolean) -> Unit, private val falsingCollector: FalsingCollector, private val falsingManager: FalsingManager, private val logSmartspaceImpression: () -> Unit @@ -473,7 +473,7 @@ class MediaCarouselScrollHandler( if (oldIndex != visibleMediaIndex && visibleToUser) { logSmartspaceImpression() } - closeGuts() + closeGuts(false) updatePlayerVisibilities() } val relativeLocation = visibleMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index fe3463f853f0..3e9559b1a9ec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -25,7 +25,8 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; -import android.graphics.PorterDuff; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -212,7 +213,8 @@ public class MediaControlPanel { mMediaViewController.openGuts(); return true; } else { - return false; + closeGuts(); + return true; } }); mPlayerViewHolder.getCancel().setOnClickListener(v -> { @@ -276,7 +278,7 @@ public class MediaControlPanel { if (mMediaViewController.isGutsVisible()) return; logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK - false); + /* isRecommendationCard */ false); mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, buildLaunchAnimatorController(mPlayerViewHolder.getPlayer())); }); @@ -384,7 +386,7 @@ public class MediaControlPanel { button.setEnabled(true); button.setOnClickListener(v -> { logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK - false); + /* isRecommendationCard */ false); action.run(); }); } @@ -418,7 +420,7 @@ public class MediaControlPanel { mPlayerViewHolder.getDismiss().setEnabled(isDismissible); mPlayerViewHolder.getDismiss().setOnClickListener(v -> { logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS - false); + /* isRecommendationCard */ false); if (mKey != null) { closeGuts(); @@ -472,10 +474,7 @@ public class MediaControlPanel { } /** Bind this recommendation view based on the data given. */ - public void bindRecommendation( - @NonNull SmartspaceTarget target, - @NonNull int backgroundColor, - @Nullable View.OnClickListener callback) { + public void bindRecommendation(@NonNull SmartspaceTarget target, @NonNull int backgroundColor) { if (mRecommendationViewHolder == null) { return; } @@ -515,8 +514,9 @@ public class MediaControlPanel { // Get the logo from app's package name when applicable. String packageName = extras.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME); try { - icon = mContext.getPackageManager().getApplicationIcon( + Drawable drawable = mContext.getPackageManager().getApplicationIcon( packageName); + icon = convertToGrayscale(drawable); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "No media source icon can be fetched via package name", e); } @@ -528,18 +528,13 @@ public class MediaControlPanel { // Set up media source app's logo. ImageView mediaSourceLogoImageView = mediaLogoItems.get(uiComponentIndex); mediaSourceLogoImageView.setImageDrawable(icon); - // TODO(b/186699032): Tint the app logo using the accent color. - mediaSourceLogoImageView.setColorFilter(backgroundColor, PorterDuff.Mode.XOR); // Set up media item cover. ImageView mediaCoverImageView = mediaCoverItems.get(uiComponentIndex); mediaCoverImageView.setImageIcon(recommendation.getIcon()); // Set up the click listener if applicable. - setSmartspaceRecItemOnClickListener( - mediaCoverImageView, - recommendation, - callback); + setSmartspaceRecItemOnClickListener(mediaCoverImageView, recommendation); if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) { setVisibleAndAlpha(collapsedSet, @@ -563,7 +558,7 @@ public class MediaControlPanel { // Set up long press to show guts setting panel. mRecommendationViewHolder.getDismiss().setOnClickListener(v -> { logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS - true); + /* isRecommendationCard */ true); closeGuts(); mKeyguardDismissUtil.executeWhenUnlocked(() -> { mMediaDataManagerLazy.get().dismissSmartspaceRecommendation( @@ -651,6 +646,15 @@ public class MediaControlPanel { return (state.getState() == PlaybackState.STATE_PLAYING); } + /** Convert the pass-in source drawable to a grayscale one. */ + private Drawable convertToGrayscale(Drawable drawable) { + ColorMatrix matrix = new ColorMatrix(); + matrix.setSaturation(0); + ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix); + drawable.setColorFilter(filter); + return drawable; + } + private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) { set.setVisibility(actionId, visible ? ConstraintSet.VISIBLE : ConstraintSet.GONE); set.setAlpha(actionId, visible ? 1.0f : 0.0f); @@ -658,8 +662,7 @@ public class MediaControlPanel { private void setSmartspaceRecItemOnClickListener( @NonNull View view, - @NonNull SmartspaceAction action, - @Nullable View.OnClickListener callback) { + @NonNull SmartspaceAction action) { if (view == null || action == null || action.getIntent() == null) { Log.e(TAG, "No tap action can be set up"); return; @@ -668,7 +671,7 @@ public class MediaControlPanel { view.setOnClickListener(v -> { // When media recommendation card is shown, it will always be the top card. logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK - true); + /* isRecommendationCard */ true); if (shouldSmartspaceRecItemOpenInForeground(action)) { // Request to unlock the device if the activity needs to be opened in foreground. @@ -682,9 +685,8 @@ public class MediaControlPanel { view.getContext().startActivity(action.getIntent()); } - if (callback != null) { - callback.onClick(v); - } + // Automatically scroll to the active player once the media is loaded. + mMediaCarouselController.setShouldScrollToActivePlayer(true); }); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 3c28f6e1651a..60e832ace7b0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -273,6 +273,7 @@ class MediaHierarchyManager @Inject constructor( } else { updateDesiredLocation() qsExpanded = false + closeGuts() } mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser() } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java index 2d0d5cd73aac..40f908b0c62f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java @@ -16,7 +16,6 @@ package com.android.systemui.navigationbar; -import android.annotation.ColorInt; import android.content.Context; import android.view.View; @@ -46,10 +45,9 @@ public class NavigationBarOverlayController { } /** - * Initialize the controller with visibility change callback and light/dark icon color. + * Initialize the controller with visibility change callback. */ - public void init(Consumer<Boolean> visibilityChangeCallback, @ColorInt int lightIconColor, - @ColorInt int darkIconColor) {} + public void init(Consumer<Boolean> visibilityChangeCallback) {} /** * Set whether the view can be shown. @@ -72,11 +70,6 @@ public class NavigationBarOverlayController { public void unregisterListeners() {} /** - * Set the dark intensity for all drawables. - */ - public void setDarkIntensity(float darkIntensity) {} - - /** * Return the current view. */ public View getCurrentView() { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java index fbc7c92a4a64..b4f8c10f3fa6 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java @@ -198,7 +198,6 @@ public final class NavigationBarTransitions extends BarTransitions implements } mView.getRotationButtonController().setDarkIntensity(darkIntensity); - Dependency.get(NavigationBarOverlayController.class).setDarkIntensity(darkIntensity); for (DarkIntensityListener listener : mDarkIntensityListeners) { listener.onDarkIntensity(darkIntensity); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index f82d265d12d6..fcbd59659ae4 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -61,7 +61,6 @@ import android.view.WindowInsetsController.Behavior; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; -import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; import com.android.internal.annotations.VisibleForTesting; @@ -326,8 +325,7 @@ public class NavigationBarView extends FrameLayout implements mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService); mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class); if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { - mNavBarOverlayController.init( - mNavbarOverlayVisibilityChangeCallback, mLightIconColor, mDarkIconColor); + mNavBarOverlayController.init(mNavbarOverlayVisibilityChangeCallback); } mConfiguration = new Configuration(); diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java index 0f6645641dda..b55d86e8fb1c 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java @@ -18,7 +18,9 @@ package com.android.systemui.people; import android.content.ContentProvider; import android.content.ContentValues; +import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; import android.os.Binder; @@ -27,16 +29,19 @@ import android.os.UserHandle; import android.util.Log; import android.widget.RemoteViews; +import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.shared.system.PeopleProviderUtils; import javax.inject.Inject; /** API that returns a People Tile preview. */ -public class PeopleProvider extends ContentProvider { +public class PeopleProvider extends ContentProvider implements + SystemUIAppComponentFactory.ContextInitializer { private static final String TAG = "PeopleProvider"; private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; private static final String EMPTY_STRING = ""; + private SystemUIAppComponentFactory.ContextAvailableCallback mCallback; @Inject PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; @@ -82,8 +87,8 @@ public class PeopleProvider extends ContentProvider { Log.e(TAG, "Could not initialize people widget manager"); return null; } - RemoteViews view = - mPeopleSpaceWidgetManager.getPreview(shortcutId, userHandle, packageName, extras); + RemoteViews view = mPeopleSpaceWidgetManager.getPreview(shortcutId, userHandle, packageName, + extras); if (view == null) { if (DEBUG) Log.d(TAG, "No preview available for shortcutId: " + shortcutId); return null; @@ -130,5 +135,17 @@ public class PeopleProvider extends ContentProvider { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new IllegalArgumentException("Invalid method"); } + + @Override + public void attachInfo(Context context, ProviderInfo info) { + mCallback.onContextAvailable(context); + super.attachInfo(context, info); + } + + @Override + public void setContextAvailableCallback( + SystemUIAppComponentFactory.ContextAvailableCallback callback) { + mCallback = callback; + } } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 29685a4c628b..7b5ab0df1e5e 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -416,9 +416,8 @@ public class PeopleSpaceUtils { && birthdayString == null; boolean addBirthdayStatus = !hasBirthdayStatus(storedTile, context) && birthdayString != null; - boolean shouldUpdate = - storedTile.getContactAffinity() != affinity || outdatedBirthdayStatus - || addBirthdayStatus; + boolean shouldUpdate = storedTile.getContactAffinity() != affinity || outdatedBirthdayStatus + || addBirthdayStatus; if (shouldUpdate) { if (DEBUG) Log.d(TAG, "Update " + storedTile.getUserName() + " from contacts"); manager.updateAppWidgetOptionsAndView(appWidgetId, diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 06f8a60dd015..9fc9cad0a690 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -41,8 +41,6 @@ import android.app.people.ConversationStatus; import android.app.people.PeopleSpaceTile; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; @@ -57,13 +55,13 @@ import android.text.TextUtils; import android.util.IconDrawableFactory; import android.util.Log; import android.util.Pair; +import android.view.Gravity; import android.view.View; import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.launcher3.icons.FastBitmapDrawable; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; @@ -195,16 +193,10 @@ public class PeopleTileViewHelper { */ private RemoteViews getViewForTile() { if (DEBUG) Log.d(TAG, "Creating view for tile key: " + mKey.toString()); - if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted()) { - if (DEBUG) Log.d(TAG, "Create empty view: " + mTile); - return createEmptyView(); - } - - boolean dndBlockingTileData = isDndBlockingTileData(mTile); - if (dndBlockingTileData) { - if (DEBUG) Log.d(TAG, "Create DND view: " + mTile.getNotificationPolicyState()); - // TODO: Create DND view. - return createEmptyView(); + if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted() + || isDndBlockingTileData(mTile)) { + if (DEBUG) Log.d(TAG, "Create suppressed view: " + mTile); + return createSuppressedView(); } if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) { @@ -265,34 +257,27 @@ public class PeopleTileViewHelper { return !tile.canBypassDnd(); } - private RemoteViews createEmptyView() { - RemoteViews views = new RemoteViews(mContext.getPackageName(), - R.layout.people_tile_empty_layout); - Drawable appIcon = getAppBadge(mKey.getPackageName(), mKey.getUserId()); + private RemoteViews createSuppressedView() { + RemoteViews views; + if (mTile.isUserQuieted()) { + views = new RemoteViews(mContext.getPackageName(), + R.layout.people_tile_work_profile_quiet_layout); + } else { + views = new RemoteViews(mContext.getPackageName(), + R.layout.people_tile_suppressed_layout); + } + Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon); Bitmap appIconAsBitmap = convertDrawableToBitmap(appIcon); - FastBitmapDrawable drawable = new FastBitmapDrawable( - appIconAsBitmap); + FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap); drawable.setIsDisabled(true); Bitmap convertedBitmap = convertDrawableToBitmap(drawable); - views.setImageViewBitmap(R.id.item, convertedBitmap); + views.setImageViewBitmap(R.id.icon, convertedBitmap); return views; } - private Drawable getAppBadge(String packageName, int userId) { - Drawable badge = null; - try { - final ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfoAsUser( - packageName, PackageManager.GET_META_DATA, userId); - badge = Utils.getBadgedIcon(mContext, appInfo); - } catch (PackageManager.NameNotFoundException e) { - badge = mContext.getPackageManager().getDefaultActivityIcon(); - } - return badge; - } - private void setMaxLines(RemoteViews views, boolean showSender) { int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp( - R.dimen.content_text_size_for_medium) + R.dimen.content_text_size_for_large) : getSizeInDp(R.dimen.content_text_size_for_medium); int lineHeight = getLineHeight(textSize); int notificationContentHeight = getContentHeightForLayout(lineHeight); @@ -422,9 +407,6 @@ public class PeopleTileViewHelper { views.setViewVisibility(R.id.availability, View.GONE); } - if (mTile.getUserName() != null) { - views.setTextViewText(R.id.name, mTile.getUserName().toString()); - } views.setBoolean(R.id.image, "setClipToOutline", true); views.setImageViewBitmap(R.id.person_icon, getPersonIconBitmap(mContext, mTile, maxAvatarSize)); @@ -537,25 +519,31 @@ public class PeopleTileViewHelper { statusText = getStatusTextByType(status.getActivity()); } views.setViewVisibility(R.id.predefined_icon, View.VISIBLE); - views.setViewVisibility(R.id.messages_count, View.GONE); - setMaxLines(views, false); - // Secondary text color for statuses. - views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorSecondary); views.setTextViewText(R.id.text_content, statusText); + if (mLayoutSize == LAYOUT_LARGE) { + views.setInt(R.id.content, "setGravity", Gravity.BOTTOM); + } Icon statusIcon = status.getIcon(); if (statusIcon != null) { - // No multi-line text with status images on medium layout. - views.setViewVisibility(R.id.text_content, View.GONE); + // No text content styled text on medium or large. + views.setViewVisibility(R.id.scrim_layout, View.VISIBLE); + views.setImageViewIcon(R.id.status_icon, statusIcon); // Show 1-line subtext on large layout with status images. if (mLayoutSize == LAYOUT_LARGE) { - views.setViewVisibility(R.id.subtext, View.VISIBLE); - views.setTextViewText(R.id.subtext, statusText); + if (DEBUG) Log.d(TAG, "Remove name for large"); + views.setViewVisibility(R.id.name, View.GONE); + views.setColorAttr(R.id.text_content, "setTextColor", + android.R.attr.textColorPrimary); + } else if (mLayoutSize == LAYOUT_MEDIUM) { + views.setViewVisibility(R.id.text_content, View.GONE); + views.setTextViewText(R.id.name, statusText); } - views.setViewVisibility(R.id.image, View.VISIBLE); - views.setImageViewIcon(R.id.image, statusIcon); } else { - views.setViewVisibility(R.id.image, View.GONE); + // Secondary text color for statuses without icons. + views.setColorAttr(R.id.text_content, "setTextColor", + android.R.attr.textColorSecondary); + setMaxLines(views, false); } // TODO: Set status pre-defined icons views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person); @@ -741,16 +729,23 @@ public class PeopleTileViewHelper { views.setViewVisibility(R.id.name, View.VISIBLE); views.setViewVisibility(R.id.text_content, View.VISIBLE); views.setViewVisibility(R.id.subtext, View.GONE); + views.setViewVisibility(R.id.image, View.GONE); + views.setViewVisibility(R.id.scrim_layout, View.GONE); } if (mLayoutSize == LAYOUT_MEDIUM) { if (DEBUG) Log.d(TAG, "Set vertical padding: " + mMediumVerticalPadding); int horizontalPadding = (int) Math.floor(MAX_MEDIUM_PADDING * mDensity); int verticalPadding = (int) Math.floor(mMediumVerticalPadding * mDensity); - views.setViewPadding(R.id.item, horizontalPadding, verticalPadding, horizontalPadding, + views.setViewPadding(R.id.content, horizontalPadding, verticalPadding, + horizontalPadding, verticalPadding); } views.setViewVisibility(R.id.messages_count, View.GONE); + if (mTile.getUserName() != null) { + views.setTextViewText(R.id.name, mTile.getUserName()); + } + return views; } @@ -761,6 +756,9 @@ public class PeopleTileViewHelper { views.setViewVisibility(R.id.predefined_icon, View.GONE); views.setViewVisibility(R.id.messages_count, View.GONE); } + if (mTile.getUserName() != null) { + views.setTextViewText(R.id.name, mTile.getUserName()); + } String status = getLastInteractionString(mContext, mTile.getLastInteractionTimestamp()); if (status != null) { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt index eec69f98b9be..1d2e74703b42 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt @@ -28,7 +28,7 @@ class PrivacyChipBuilder(private val context: Context, itemsList: List<PrivacyIt appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType }) .toList() .sortedWith(compareBy({ -it.second.size }, // Sort by number of AppOps - { it.second.minOrNull() })) // Sort by "smallest" AppOpp (Location is largest) + { it.second.min() })) // Sort by "smallest" AppOpp (Location is largest) types = itemsList.map { it.privacyType }.distinct().sorted() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index dd1a4af18f7f..3a3f3f1ca7ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -129,6 +129,12 @@ public class QSContainerImpl extends FrameLayout { @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { mNavBarInset = insets.getInsets(WindowInsets.Type.navigationBars()).bottom; + mQSPanelContainer.setPaddingRelative( + mQSPanelContainer.getPaddingStart(), + mQSPanelContainer.getPaddingTop(), + mQSPanelContainer.getPaddingEnd(), + mNavBarInset + ); return super.onApplyWindowInsets(insets); } @@ -138,8 +144,7 @@ public class QSContainerImpl extends FrameLayout { // bottom and footer are inside the screen. MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); - int availableScreenHeight = getDisplayHeight() - mNavBarInset; - int maxQs = availableScreenHeight - layoutParams.topMargin - layoutParams.bottomMargin + int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin - getPaddingBottom(); int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin + layoutParams.rightMargin; @@ -148,10 +153,8 @@ public class QSContainerImpl extends FrameLayout { mQSPanelContainer.measure(qsPanelWidthSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); int width = mQSPanelContainer.getMeasuredWidth() + padding; - int height = layoutParams.topMargin + layoutParams.bottomMargin - + mQSPanelContainer.getMeasuredHeight() + getPaddingBottom(); super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(availableScreenHeight, MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY)); // QSCustomizer will always be the height of the screen, but do this after // other measuring to avoid changing the height of the QS. mQSCustomizer.measure(widthMeasureSpec, @@ -200,12 +203,6 @@ public class QSContainerImpl extends FrameLayout { layoutParams.topMargin = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_offset_height); mQSPanelContainer.setLayoutParams(layoutParams); - mQSPanelContainer.setPaddingRelative( - mQSPanelContainer.getPaddingStart(), - mQSPanelContainer.getPaddingTop(), - mQSPanelContainer.getPaddingEnd(), - mContext.getResources().getDimensionPixelSize(R.dimen.qs_container_bottom_padding) - ); int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); int padding = getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 05197e46fb25..0335319f5b49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -30,6 +30,7 @@ import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; +import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; import android.widget.LinearLayout; @@ -153,11 +154,18 @@ public class QSDetail extends LinearLayout { MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); lp.topMargin = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_offset_height); - lp.bottomMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.qs_container_bottom_padding); setLayoutParams(lp); } + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + int bottomNavBar = insets.getInsets(WindowInsets.Type.navigationBars()).bottom; + MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); + lp.bottomMargin = bottomNavBar; + setLayoutParams(lp); + return super.onApplyWindowInsets(insets); + } + public boolean isClosingDetail() { return mClosingDetail; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 1fa926009861..f6d93895ce05 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -29,6 +29,7 @@ import android.widget.TextView; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; @@ -73,6 +74,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final View mPowerMenuLite; private final boolean mShowPMLiteButton; private GlobalActionsDialogLite mGlobalActionsDialog; + private final UiEventLogger mUiEventLogger; private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = new UserInfoController.OnUserInfoChangedListener() { @@ -122,6 +124,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme startSettingsActivity(); } } else if (v == mPowerMenuLite) { + mUiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS); mGlobalActionsDialog.showOrHideDialog(false, true); } } @@ -139,7 +142,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme QuickQSPanelController quickQSPanelController, TunerService tunerService, MetricsLogger metricsLogger, FalsingManager falsingManager, @Named(PM_LITE_ENABLED) boolean showPMLiteButton, - GlobalActionsDialogLite globalActionsDialog) { + GlobalActionsDialogLite globalActionsDialog, UiEventLogger uiEventLogger) { super(view); mUserManager = userManager; mUserInfoController = userInfoController; @@ -161,6 +164,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mPowerMenuLite = mView.findViewById(R.id.pm_lite); mShowPMLiteButton = showPMLiteButton; mGlobalActionsDialog = globalActionsDialog; + mUiEventLogger = uiEventLogger; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 04e32a10db17..3a6f1d5a02ae 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -190,6 +190,16 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen || vpnName != null || vpnNameWorkProfile != null || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled || (hasWorkProfile && isNetworkLoggingEnabled); + // Update the view to be untappable if the device is an organization-owned device with a + // managed profile and there is no policy set which requires a privacy disclosure. + if (mIsVisible && isProfileOwnerOfOrganizationOwnedDevice && !isNetworkLoggingEnabled + && !hasCACertsInWorkProfile && vpnNameWorkProfile == null) { + mRootView.setClickable(false); + mRootView.findViewById(R.id.footer_icon).setVisibility(View.GONE); + } else { + mRootView.setClickable(true); + mRootView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE); + } // Update the string mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile, hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName, @@ -345,20 +355,15 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen private View createOrganizationDialogView() { final boolean isDeviceManaged = mSecurityController.isDeviceManaged(); - boolean isProfileOwnerOfOrganizationOwnedDevice = - mSecurityController.isProfileOwnerOfOrganizationOwnedDevice(); final boolean hasWorkProfile = mSecurityController.hasWorkProfile(); final CharSequence deviceOwnerOrganization = mSecurityController.getDeviceOwnerOrganizationName(); - final CharSequence workProfileOrganizationName = - mSecurityController.getWorkProfileOrganizationName(); final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser(); final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile(); final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); final String vpnName = mSecurityController.getPrimaryVpnName(); final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName(); - View dialogView = LayoutInflater.from(mContext) .inflate(R.layout.quick_settings_footer_dialog, null, false); @@ -368,8 +373,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen deviceManagementSubtitle.setText(getManagementTitle(deviceOwnerOrganization)); CharSequence managementMessage = getManagementMessage(isDeviceManaged, - deviceOwnerOrganization, isProfileOwnerOfOrganizationOwnedDevice, - workProfileOrganizationName); + deviceOwnerOrganization); if (managementMessage == null) { dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.GONE); } else { @@ -377,11 +381,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen TextView deviceManagementWarning = (TextView) dialogView.findViewById(R.id.device_management_warning); deviceManagementWarning.setText(managementMessage); - // Don't show the policies button for profile owner of org owned device, because there - // is no policies settings screen for it - if (!isProfileOwnerOfOrganizationOwnedDevice) { - mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this); - } + mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this); } // ca certificate section @@ -496,12 +496,11 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen } protected CharSequence getManagementMessage(boolean isDeviceManaged, - CharSequence organizationName, boolean isProfileOwnerOfOrganizationOwnedDevice, - CharSequence workProfileOrganizationName) { - if (!isDeviceManaged && !isProfileOwnerOfOrganizationOwnedDevice) { + CharSequence organizationName) { + if (!isDeviceManaged) { return null; } - if (isDeviceManaged && organizationName != null) { + if (organizationName != null) { if (isFinancedDevice()) { return mContext.getString(R.string.monitoring_financed_description_named_management, organizationName, organizationName); @@ -509,9 +508,6 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen return mContext.getString( R.string.monitoring_description_named_management, organizationName); } - } else if (isProfileOwnerOfOrganizationOwnedDevice && workProfileOrganizationName != null) { - return mContext.getString( - R.string.monitoring_description_named_management, workProfileOrganizationName); } return mContext.getString(R.string.monitoring_description_management); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 73a6b34c612e..81b5318d8089 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -23,7 +23,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.MathUtils; +import android.util.FeatureFlagUtils; import android.util.Pair; import android.view.DisplayCutout; import android.view.View; @@ -36,9 +36,7 @@ import android.widget.Space; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; import com.android.systemui.qs.QSDetail.Callback; -import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.phone.StatusIconContainer; @@ -67,7 +65,11 @@ public class QuickStatusBarHeader extends FrameLayout { private View mQSCarriers; private Clock mClockView; - private Space mSpace; + private Space mDatePrivacySeparator; + private View mClockIconsSeparator; + private boolean mShowClockIconsSeparator; + private ViewGroup mRightLayout; + private BatteryMeterView mBatteryRemainingIcon; private StatusIconContainer mIconContainer; private View mPrivacyChip; @@ -80,19 +82,27 @@ public class QuickStatusBarHeader extends FrameLayout { private int mWaterfallTopInset; private int mCutOutPaddingLeft; private int mCutOutPaddingRight; - private float mClockIconsAlpha = 1.0f; + private float mViewAlpha = 1.0f; private float mKeyguardExpansionFraction; private int mTextColorPrimary = Color.TRANSPARENT; private int mTopViewMeasureHeight; private final String mMobileSlotName; + private final String mNoCallingSlotName; private final String mCallStrengthSlotName; + private final boolean mProviderModel; public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); - mMobileSlotName = context.getString(com.android.internal.R.string.status_bar_no_calling); + mMobileSlotName = context.getString(com.android.internal.R.string.status_bar_mobile); + mNoCallingSlotName = context.getString(com.android.internal.R.string.status_bar_no_calling); mCallStrengthSlotName = context.getString(com.android.internal.R.string.status_bar_call_strength); + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + mProviderModel = true; + } else { + mProviderModel = false; + } } /** @@ -117,9 +127,11 @@ public class QuickStatusBarHeader extends FrameLayout { mPrivacyChip = findViewById(R.id.privacy_chip); mDateView = findViewById(R.id.date); mSecurityHeaderView = findViewById(R.id.header_text_container); + mClockIconsSeparator = findViewById(R.id.separator); + mRightLayout = findViewById(R.id.rightLayout); mClockView = findViewById(R.id.clock); - mSpace = findViewById(R.id.space); + mDatePrivacySeparator = findViewById(R.id.space); // Tint for the battery icons are handled in setupHost() mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon); @@ -230,36 +242,50 @@ public class QuickStatusBarHeader extends FrameLayout { } private void updateAlphaAnimator() { - StatusBarIconView noCallingIcon = - ((StatusBarIconView) mIconContainer.getViewForSlot(mMobileSlotName)); - StatusBarIconView callStrengthIcon = - ((StatusBarIconView) mIconContainer.getViewForSlot(mCallStrengthSlotName)); TouchAnimator.Builder builder = new TouchAnimator.Builder() // The following two views have to be hidden manually, so as not to hide the // Privacy chip in QQS .addFloat(mDateView, "alpha", 0, 1) .addFloat(mSecurityHeaderView, "alpha", 0, 1) - .addFloat(mQSCarriers, "alpha", 0, 1); - builder.setListener(new TouchAnimator.ListenerAdapter() { - @Override - public void onAnimationAtEnd() { - mIconContainer.addIgnoredSlot(mMobileSlotName); - mIconContainer.addIgnoredSlot(mCallStrengthSlotName); - } - - @Override - public void onAnimationStarted() { - mIconContainer.addIgnoredSlot(mMobileSlotName); - mIconContainer.addIgnoredSlot(mCallStrengthSlotName); - } - - @Override - public void onAnimationAtStart() { - super.onAnimationAtStart(); - mIconContainer.removeIgnoredSlot(mMobileSlotName); - mIconContainer.removeIgnoredSlot(mCallStrengthSlotName); - } - }); + .addFloat(mQSCarriers, "alpha", 0, 1) + .setListener(new TouchAnimator.ListenerAdapter() { + @Override + public void onAnimationAtEnd() { + // TODO(b/185580157): Remove the mProviderModel if the mobile slot can be + // hidden in Provider model. + if (mProviderModel) { + mIconContainer.addIgnoredSlot(mNoCallingSlotName); + mIconContainer.addIgnoredSlot(mCallStrengthSlotName); + } else { + mIconContainer.addIgnoredSlot(mMobileSlotName); + } + } + + @Override + public void onAnimationStarted() { + if (mProviderModel) { + mIconContainer.addIgnoredSlot(mNoCallingSlotName); + mIconContainer.addIgnoredSlot(mCallStrengthSlotName); + } else { + mIconContainer.addIgnoredSlot(mMobileSlotName); + } + + setSeparatorVisibility(false); + } + + @Override + public void onAnimationAtStart() { + super.onAnimationAtStart(); + if (mProviderModel) { + mIconContainer.removeIgnoredSlot(mNoCallingSlotName); + mIconContainer.removeIgnoredSlot(mCallStrengthSlotName); + } else { + mIconContainer.removeIgnoredSlot(mMobileSlotName); + } + + setSeparatorVisibility(mShowClockIconsSeparator); + } + }); mAlphaAnimator = builder.build(); } @@ -337,20 +363,30 @@ public class QuickStatusBarHeader extends FrameLayout { cutout, cornerCutoutPadding, -1); mDatePrivacyView.setPadding(padding.first, 0, padding.second, 0); mClockIconsView.setPadding(padding.first, 0, padding.second, 0); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpace.getLayoutParams(); + LinearLayout.LayoutParams datePrivacySeparatorLayoutParams = + (LinearLayout.LayoutParams) mDatePrivacySeparator.getLayoutParams(); + LinearLayout.LayoutParams mClockIconsSeparatorLayoutParams = + (LinearLayout.LayoutParams) mClockIconsSeparator.getLayoutParams(); boolean cornerCutout = cornerCutoutPadding != null && (cornerCutoutPadding.first == 0 || cornerCutoutPadding.second == 0); if (cutout != null) { Rect topCutout = cutout.getBoundingRectTop(); if (topCutout.isEmpty() || cornerCutout) { - lp.width = 0; - mSpace.setVisibility(View.GONE); + datePrivacySeparatorLayoutParams.width = 0; + mDatePrivacySeparator.setVisibility(View.GONE); + mClockIconsSeparatorLayoutParams.width = 0; + setSeparatorVisibility(false); + mShowClockIconsSeparator = false; } else { - lp.width = topCutout.width(); - mSpace.setVisibility(View.VISIBLE); + datePrivacySeparatorLayoutParams.width = topCutout.width(); + mDatePrivacySeparator.setVisibility(View.VISIBLE); + mClockIconsSeparatorLayoutParams.width = topCutout.width(); + mShowClockIconsSeparator = true; + setSeparatorVisibility(mKeyguardExpansionFraction == 0f); } } - mSpace.setLayoutParams(lp); + mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams); + mClockIconsSeparator.setLayoutParams(mClockIconsSeparatorLayoutParams); mCutOutPaddingLeft = padding.first; mCutOutPaddingRight = padding.second; mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top; @@ -358,6 +394,32 @@ public class QuickStatusBarHeader extends FrameLayout { return super.onApplyWindowInsets(insets); } + /** + * Sets the visibility of the separator between clock and icons. + * + * This separator is "visible" when there is a center cutout, to block that space. In that + * case, the clock and the layout on the right (containing the icons and the battery meter) are + * set to weight 1 to take the available space. + * @param visible whether the separator between clock and icons should be visible. + */ + private void setSeparatorVisibility(boolean visible) { + int newVisibility = visible ? View.VISIBLE : View.GONE; + if (mClockIconsSeparator.getVisibility() == newVisibility) return; + + mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE); + mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE); + + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams(); + lp.width = visible ? 0 : WRAP_CONTENT; + lp.weight = visible ? 1f : 0f; + mClockView.setLayoutParams(lp); + + lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams(); + lp.width = visible ? 0 : WRAP_CONTENT; + lp.weight = visible ? 1f : 0f; + mRightLayout.setLayoutParams(lp); + } + private void updateHeadersPadding() { setContentMargins(mDatePrivacyView, 0, 0); setContentMargins(mClockIconsView, 0, 0); @@ -408,35 +470,12 @@ public class QuickStatusBarHeader extends FrameLayout { } /** - * When QS is scrolling, mClockIconsAlpha should scroll away and fade out. - * - * For a given scroll level, this method does the following: - * <ol> - * <li>Determine the alpha that {@code mClockIconsView} should have when the panel is fully - * expanded.</li> - * <li>Set the scroll of {@code mClockIconsView} to the same of {@code QSPanel}.</li> - * <li>Set the alpha of {@code mClockIconsView} to that determined by the expansion of - * the panel, interpolated between 1 (no expansion) and {@code mClockIconsAlpha} (fully - * expanded), matching the animator.</li> - * </ol> + * Scroll the headers away. * * @param scrollY the scroll of the QSPanel container */ public void setExpandedScrollAmount(int scrollY) { - // The scrolling of the expanded qs has changed. Since the header text isn't part of it, - // but would overlap content, we're fading it out. - float newAlpha = 1.0f; - if (mClockIconsView.getHeight() > 0) { - newAlpha = MathUtils.map(0, mClockIconsView.getHeight() / 2.0f, 1.0f, 0.0f, - scrollY); - newAlpha = Interpolators.ALPHA_OUT.getInterpolation(newAlpha); - } mClockIconsView.setScrollY(scrollY); - if (newAlpha != mClockIconsAlpha) { - mClockIconsAlpha = newAlpha; - mClockIconsView.setAlpha(MathUtils.lerp(1.0f, mClockIconsAlpha, - mKeyguardExpansionFraction)); - updateAlphaAnimator(); - } + mDatePrivacyView.setScrollY(scrollY); } } 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 2d777a53de51..b3ec39f4f40a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.tileimpl +import android.animation.ArgbEvaluator +import android.animation.PropertyValuesHolder import android.animation.ValueAnimator import android.content.Context import android.content.res.ColorStateList @@ -43,6 +45,7 @@ import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.plugins.qs.QSTileView +import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH import java.util.Objects private const val TAG = "QSTileViewImpl" @@ -54,6 +57,10 @@ open class QSTileViewImpl @JvmOverloads constructor( companion object { private const val INVALID = -1 + private const val BACKGROUND_NAME = "background" + private const val LABEL_NAME = "label" + private const val SECONDARY_LABEL_NAME = "secondaryLabel" + private const val CHEVRON_NAME = "chevron" } override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE @@ -83,9 +90,19 @@ open class QSTileViewImpl @JvmOverloads constructor( private lateinit var ripple: RippleDrawable private lateinit var colorBackgroundDrawable: Drawable private var paintColor: Int = 0 - private var paintAnimator: ValueAnimator? = null - private var labelAnimator: ValueAnimator? = null - private var secondaryLabelAnimator: ValueAnimator? = null + private val singleAnimator: ValueAnimator = ValueAnimator().apply { + setDuration(QS_ANIM_LENGTH) + addUpdateListener { animation -> + setAllColors( + // These casts will throw an exception if some property is missing. We should + // always have all properties. + animation.getAnimatedValue(BACKGROUND_NAME) as Int, + animation.getAnimatedValue(LABEL_NAME) as Int, + animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int, + animation.getAnimatedValue(CHEVRON_NAME) as Int + ) + } + } private var accessibilityClass: String? = null private var stateDescriptionDeltas: CharSequence? = null @@ -104,8 +121,7 @@ open class QSTileViewImpl @JvmOverloads constructor( clipToPadding = false isFocusable = true background = createTileBackground() - paintColor = getCircleColor(QSTile.State.DEFAULT_STATE) - colorBackgroundDrawable.setTint(paintColor) + setColor(getBackgroundColorForState(QSTile.State.DEFAULT_STATE)) val padding = resources.getDimensionPixelSize(R.dimen.qs_tile_padding) val startPadding = resources.getDimensionPixelSize(R.dimen.qs_tile_start_padding) @@ -166,8 +182,8 @@ open class QSTileViewImpl @JvmOverloads constructor( labelContainer.ignoreLastView = true secondaryLabel.alpha = 0f } - label.setTextColor(getLabelColor(QSTile.State.DEFAULT_STATE)) - secondaryLabel.setTextColor(getSecondaryLabelColor(QSTile.State.DEFAULT_STATE)) + setLabelColor(getLabelColorForState(QSTile.State.DEFAULT_STATE)) + setSecondaryLabelColor(getSecondaryLabelColorForState(QSTile.State.DEFAULT_STATE)) addView(labelContainer) } @@ -176,6 +192,7 @@ open class QSTileViewImpl @JvmOverloads constructor( .inflate(R.layout.qs_tile_side_icon, this, false) as ViewGroup customDrawableView = sideView.requireViewById(R.id.customDrawable) chevronView = sideView.requireViewById(R.id.chevron) + setChevronColor(getChevronColorForState(QSTile.State.DEFAULT_STATE)) addView(sideView) } @@ -322,19 +339,6 @@ open class QSTileViewImpl @JvmOverloads constructor( icon.setIcon(state, allowAnimations) contentDescription = state.contentDescription - // Background color animation - val newColor = getCircleColor(state.state) - if (allowAnimations) { - animateBackground(newColor) - } else { - clearBackgroundAnimator() - colorBackgroundDrawable.setTintList(ColorStateList.valueOf(newColor)).also { - paintColor = newColor - } - paintColor = newColor - } - // - // State handling and description val stateDescription = StringBuilder() val stateText = getStateText(state) @@ -383,23 +387,80 @@ open class QSTileViewImpl @JvmOverloads constructor( } } - if (allowAnimations) { - animateLabelColor(getLabelColor(state.state)) - animateSecondaryLabelColor(getSecondaryLabelColor(state.state)) - } else { - label.setTextColor(getLabelColor(state.state)) - secondaryLabel.setTextColor(getSecondaryLabelColor(state.state)) + // Colors + if (state.state != lastState) { + singleAnimator.cancel() + if (allowAnimations) { + singleAnimator.setValues( + colorValuesHolder( + BACKGROUND_NAME, + paintColor, + getBackgroundColorForState(state.state) + ), + colorValuesHolder( + LABEL_NAME, + label.currentTextColor, + getLabelColorForState(state.state) + ), + colorValuesHolder( + SECONDARY_LABEL_NAME, + label.currentTextColor, + getSecondaryLabelColorForState(state.state) + ), + colorValuesHolder( + CHEVRON_NAME, + chevronView.imageTintList?.defaultColor ?: 0, + getChevronColorForState(state.state) + ) + ) + singleAnimator.start() + } else { + setAllColors( + getBackgroundColorForState(state.state), + getLabelColorForState(state.state), + getLabelColorForState(state.state), + getChevronColorForState(state.state) + ) + } } // Right side icon loadSideViewDrawableIfNecessary(state) - chevronView.imageTintList = ColorStateList.valueOf(getSecondaryLabelColor(state.state)) label.isEnabled = !state.disabledByPolicy lastState = state.state } + private fun setAllColors( + backgroundColor: Int, + labelColor: Int, + secondaryLabelColor: Int, + chevronColor: Int + ) { + setColor(backgroundColor) + setLabelColor(labelColor) + setSecondaryLabelColor(secondaryLabelColor) + setChevronColor(chevronColor) + } + + private fun setColor(color: Int) { + colorBackgroundDrawable.setTint(color) + paintColor = color + } + + private fun setLabelColor(color: Int) { + label.setTextColor(color) + } + + private fun setSecondaryLabelColor(color: Int) { + secondaryLabel.setTextColor(color) + } + + private fun setChevronColor(color: Int) { + chevronView.imageTintList = ColorStateList.valueOf(color) + } + private fun loadSideViewDrawableIfNecessary(state: QSTile.State) { if (state.sideViewCustomDrawable != null) { customDrawableView.setImageDrawable(state.sideViewCustomDrawable) @@ -446,63 +507,7 @@ open class QSTileViewImpl @JvmOverloads constructor( return locInScreen.get(1) >= -height } - private fun animateBackground(newBackgroundColor: Int) { - if (newBackgroundColor != paintColor) { - clearBackgroundAnimator() - paintAnimator = ValueAnimator.ofArgb(paintColor, newBackgroundColor) - .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { - addUpdateListener { animation: ValueAnimator -> - val c = animation.animatedValue as Int - colorBackgroundDrawable.setTintList(ColorStateList.valueOf(c)).also { - paintColor = c - } - } - start() - } - } - } - - private fun animateLabelColor(color: Int) { - val currentColor = label.textColors.defaultColor - if (currentColor != color) { - clearLabelAnimator() - labelAnimator = ValueAnimator.ofArgb(currentColor, color) - .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { - addUpdateListener { - label.setTextColor(it.animatedValue as Int) - } - start() - } - } - } - - private fun animateSecondaryLabelColor(color: Int) { - val currentColor = secondaryLabel.textColors.defaultColor - if (currentColor != color) { - clearSecondaryLabelAnimator() - secondaryLabelAnimator = ValueAnimator.ofArgb(currentColor, color) - .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { - addUpdateListener { - secondaryLabel.setTextColor(it.animatedValue as Int) - } - start() - } - } - } - - private fun clearBackgroundAnimator() { - paintAnimator?.cancel()?.also { paintAnimator = null } - } - - private fun clearLabelAnimator() { - labelAnimator?.cancel()?.also { labelAnimator = null } - } - - private fun clearSecondaryLabelAnimator() { - secondaryLabelAnimator?.cancel()?.also { secondaryLabelAnimator = null } - } - - private fun getCircleColor(state: Int): Int { + private fun getBackgroundColorForState(state: Int): Int { return when (state) { Tile.STATE_ACTIVE -> colorActive Tile.STATE_INACTIVE -> colorInactive @@ -514,7 +519,7 @@ open class QSTileViewImpl @JvmOverloads constructor( } } - private fun getLabelColor(state: Int): Int { + private fun getLabelColorForState(state: Int): Int { return when (state) { Tile.STATE_ACTIVE -> colorLabelActive Tile.STATE_INACTIVE -> colorLabelInactive @@ -526,7 +531,7 @@ open class QSTileViewImpl @JvmOverloads constructor( } } - private fun getSecondaryLabelColor(state: Int): Int { + private fun getSecondaryLabelColorForState(state: Int): Int { return when (state) { Tile.STATE_ACTIVE -> colorLabelActive Tile.STATE_INACTIVE, Tile.STATE_UNAVAILABLE -> colorLabelUnavailable @@ -536,4 +541,12 @@ open class QSTileViewImpl @JvmOverloads constructor( } } } + + private fun getChevronColorForState(state: Int): Int = getSecondaryLabelColorForState(state) +} + +private fun colorValuesHolder(name: String, vararg values: Int): PropertyValuesHolder { + return PropertyValuesHolder.ofInt(name, *values).apply { + setEvaluator(ArgbEvaluator.getInstance()) + } }
\ No newline at end of file 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 577c0d8455eb..b7f2cd0da642 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -319,10 +319,18 @@ public class InternetTile extends QSTileImpl<SignalState> { Log.d(TAG, "setIsAirplaneMode: " + "icon = " + (icon == null ? "" : icon.toString())); } + if (mCellularInfo.mAirplaneModeEnabled == icon.visible) { + return; + } mCellularInfo.mAirplaneModeEnabled = icon.visible; mWifiInfo.mAirplaneModeEnabled = icon.visible; if (!mSignalCallback.mEthernetInfo.mConnected) { - refreshState(mCellularInfo); + if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0) + && (mWifiInfo.mSsid != null)) { + refreshState(mWifiInfo); + } else { + refreshState(mCellularInfo); + } } } @@ -456,6 +464,9 @@ public class InternetTile extends QSTileImpl<SignalState> { state.dualLabelContentDescription = r.getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); state.expandedAccessibilityClassName = Switch.class.getName(); + if (DEBUG) { + Log.d(TAG, "handleUpdateWifiState: " + "SignalState = " + state.toString()); + } } private void handleUpdateCellularState(SignalState state, Object arg) { @@ -496,6 +507,9 @@ public class InternetTile extends QSTileImpl<SignalState> { } else { state.stateDescription = state.secondaryLabel; } + if (DEBUG) { + Log.d(TAG, "handleUpdateCellularState: " + "SignalState = " + state.toString()); + } } private void handleUpdateEthernetState(SignalState state, Object arg) { @@ -508,6 +522,9 @@ public class InternetTile extends QSTileImpl<SignalState> { state.state = Tile.STATE_ACTIVE; state.icon = ResourceIcon.get(cb.mEthernetSignalIconId); state.secondaryLabel = cb.mEthernetContentDescription; + if (DEBUG) { + Log.d(TAG, "handleUpdateEthernetState: " + "SignalState = " + state.toString()); + } } private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index fa28754e41a7..30c9b44536e1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; -import android.graphics.Matrix; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; @@ -35,7 +34,6 @@ import android.text.TextUtils; import android.util.Log; import android.view.ScrollCaptureResponse; import android.view.View; -import android.view.Window; import android.widget.ImageView; import androidx.constraintlayout.widget.ConstraintLayout; @@ -68,8 +66,6 @@ public class LongScreenshotActivity extends Activity { public static final String EXTRA_CAPTURE_RESPONSE = "capture-response"; private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path"; - private static final boolean USE_SHARED_ELEMENT = false; - private final UiEventLogger mUiEventLogger; private final Executor mUiExecutor; private final Executor mBackgroundExecutor; @@ -89,6 +85,7 @@ public class LongScreenshotActivity extends Activity { private ListenableFuture<File> mCacheSaveFuture; private ListenableFuture<ImageLoader.Result> mCacheLoadFuture; + private Bitmap mOutputBitmap; private LongScreenshot mLongScreenshot; private boolean mTransitionStarted; @@ -114,7 +111,7 @@ public class LongScreenshotActivity extends Activity { public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate(savedInstanceState = " + savedInstanceState + ")"); super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); + setContentView(R.layout.long_screenshot); mPreview = requireViewById(R.id.preview); @@ -173,7 +170,6 @@ public class LongScreenshotActivity extends Activity { } }, mUiExecutor); mCacheLoadFuture = null; - return; } else { LongScreenshot longScreenshot = mLongScreenshotHolder.takeLongScreenshot(); if (longScreenshot != null) { @@ -189,7 +185,6 @@ public class LongScreenshotActivity extends Activity { Log.d(TAG, "onLongScreenshotReceived(longScreenshot=" + longScreenshot + ")"); mLongScreenshot = longScreenshot; mPreview.setImageDrawable(mLongScreenshot.getDrawable()); - mTransitionView.setImageDrawable(mLongScreenshot.getDrawable()); updateImageDimensions(); mCropView.setVisibility(View.VISIBLE); mMagnifierView.setDrawable(mLongScreenshot.getDrawable(), @@ -310,19 +305,15 @@ public class LongScreenshotActivity extends Activity { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - if (USE_SHARED_ELEMENT) { - updateImageDimensions(); - mTransitionView.setVisibility(View.VISIBLE); - // TODO: listen for transition completing instead of finishing onStop - mTransitionStarted = true; - startActivity(intent, - ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()); - } else { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivityAsUser(intent, UserHandle.CURRENT); - finishAndRemoveTask(); - } + mTransitionView.setImageBitmap(mOutputBitmap); + mTransitionView.setVisibility(View.VISIBLE); + mTransitionView.setTransitionName( + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); + // TODO: listen for transition completing instead of finishing onStop + mTransitionStarted = true; + startActivity(intent, + ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()); } private void doShare(Uri uri) { @@ -368,9 +359,11 @@ public class LongScreenshotActivity extends Activity { return; } - Bitmap output = renderBitmap(mPreview.getDrawable(), bounds); + updateImageDimensions(); + + mOutputBitmap = renderBitmap(drawable, bounds); ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( - mBackgroundExecutor, UUID.randomUUID(), output, ZonedDateTime.now()); + mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now()); exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor); } @@ -419,7 +412,6 @@ public class LongScreenshotActivity extends Activity { // The image width and height on screen int imageHeight = previewHeight; int imageWidth = previewWidth; - float scale; if (imageRatio > viewRatio) { // Image is full width and height is constrained, compute extra padding to inform // CropView @@ -428,15 +420,13 @@ public class LongScreenshotActivity extends Activity { mCropView.setExtraPadding(extraPadding + mPreview.getPaddingTop(), extraPadding + mPreview.getPaddingBottom()); imageTop += (previewHeight - imageHeight) / 2; - scale = imageHeight / bounds.height(); mCropView.setExtraPadding(extraPadding, extraPadding); mCropView.setImageWidth(previewWidth); } else { imageWidth = (int) (previewWidth * imageRatio / viewRatio); imageLeft += (previewWidth - imageWidth) / 2; - scale = imageWidth / (float) bounds.width(); // Image is full height - mCropView.setExtraPadding(mPreview.getPaddingTop(), mPreview.getPaddingBottom()); + mCropView.setExtraPadding(mPreview.getPaddingTop(), mPreview.getPaddingBottom()); mCropView.setImageWidth((int) (previewHeight * imageRatio)); } @@ -449,10 +439,5 @@ public class LongScreenshotActivity extends Activity { params.width = boundaries.width(); params.height = boundaries.height(); mTransitionView.setLayoutParams(params); - - Matrix matrix = new Matrix(); - matrix.postScale(scale, scale, 0, 0); - matrix.postTranslate(-boundaries.left, -boundaries.top); - mTransitionView.setImageMatrix(matrix); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 1ff30a32c4ef..6baacb931a68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -57,6 +57,7 @@ import androidx.annotation.NonNull; import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.util.GcUtils; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.commandline.CommandRegistry; @@ -1086,6 +1087,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< thr.start(); } + @Override + public void runGcForTest() { + // Gc sysui + GcUtils.runGcAndFinalizersSync(); + } + private final class H extends Handler { private H(Looper l) { super(l); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 7f31fddbfb6c..5437ce63475e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar; import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput; import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; -import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -35,6 +34,7 @@ import android.util.Log; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; +import com.android.systemui.statusbar.phone.StatusBar; import java.util.ArrayList; import java.util.List; @@ -46,6 +46,7 @@ import java.util.List; @SuppressLint("OverrideAbstract") public class NotificationListener extends NotificationListenerWithPlugins { private static final String TAG = "NotificationListener"; + private static final boolean DEBUG = StatusBar.DEBUG; private final Context mContext; private final NotificationManager mNotificationManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 70b3a7be02fb..ca81a7b43df6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -169,7 +169,7 @@ public class NotificationRemoteInputManager implements Dumpable { Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view); mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent); boolean started = RemoteViews.startPendingIntent(view, pendingIntent, options); - if (started) releaseNotificationIfKeptForRemoteInputHistory(entry.getKey()); + if (started) releaseNotificationIfKeptForRemoteInputHistory(entry); return started; }); } @@ -608,7 +608,11 @@ public class NotificationRemoteInputManager implements Dumpable { * (after unlock, if applicable), and will then wait a short time to allow the app to update the * notification in response to the action. */ - private void releaseNotificationIfKeptForRemoteInputHistory(String key) { + private void releaseNotificationIfKeptForRemoteInputHistory(NotificationEntry entry) { + if (entry == null) { + return; + } + final String key = entry.getKey(); if (isNotificationKeptForRemoteInputHistory(key)) { mMainHandler.postDelayed(() -> { if (isNotificationKeptForRemoteInputHistory(key)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index eb7854e63a85..491959320ab7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -61,6 +61,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.util.DeviceConfigProxy; @@ -243,11 +244,12 @@ public interface StatusBarDependenciesModule { SystemClock systemClock, ActivityStarter activityStarter, @Main Executor mainExecutor, - IActivityManager iActivityManager) { + IActivityManager iActivityManager, + OngoingCallLogger logger) { OngoingCallController ongoingCallController = new OngoingCallController( notifCollection, featureFlags, systemClock, activityStarter, mainExecutor, - iActivityManager); + iActivityManager, logger); ongoingCallController.init(); return ongoingCallController; } 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 2481ed482872..5f10e557faed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -29,13 +29,11 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.StatusBarState.SHADE +import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED import com.android.systemui.statusbar.phone.StatusBarLocationPublisher import com.android.systemui.statusbar.phone.StatusBarMarginUpdatedListener import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE -import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE -import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE -import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import java.lang.IllegalStateException import java.util.concurrent.Executor @@ -50,7 +48,7 @@ import javax.inject.Inject * will have its gravity set towards the corner (i.e., top-right corner gets top|right gravity), and * the contained ImageView will be set to center_vertical and away from the corner horizontally. The * Views will match the status bar top padding and status bar height so that the dot can appear to - * reside directly after the status bar system contents (basically to the right of the battery). + * reside directly after the status bar system contents (basically after the battery). * * NOTE: any operation that modifies views directly must run on the provided executor, because * these views are owned by ScreenDecorations and it runs in its own thread @@ -85,21 +83,27 @@ class PrivacyDotViewController @Inject constructor( // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread private var uiExecutor: DelayableExecutor? = null - private var e: DelayableExecutor? = null + + private val marginListener: StatusBarMarginUpdatedListener = + object : StatusBarMarginUpdatedListener { + override fun onStatusBarMarginUpdated(marginLeft: Int, marginRight: Int) { + setStatusBarMargins(marginLeft, marginRight) + } + } private val views: Sequence<View> get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl) init { - locationPublisher.addCallback(object : StatusBarMarginUpdatedListener { - override fun onStatusBarMarginUpdated(marginLeft: Int, marginRight: Int) { - setStatusBarMargins(marginLeft, marginRight) - } - }) + locationPublisher.addCallback(marginListener) stateController.addCallback(object : StatusBarStateController.StateListener { override fun onExpandedChanged(isExpanded: Boolean) { - setStatusBarExpanded(isExpanded) + updateStatusBarState() + } + + override fun onStateChanged(newState: Int) { + updateStatusBarState() } }) } @@ -108,6 +112,13 @@ class PrivacyDotViewController @Inject constructor( uiExecutor = e } + fun setQsExpanded(expanded: Boolean) { + dlog("setQsExpanded $expanded") + synchronized(lock) { + nextViewState = nextViewState.copy(qsExpanded = expanded) + } + } + @UiThread fun setNewRotation(rot: Int) { dlog("updateRotation: $rot") @@ -125,8 +136,8 @@ class PrivacyDotViewController @Inject constructor( val index = newCorner.cornerIndex() val h = when (rot) { - ROTATION_NONE, ROTATION_UPSIDE_DOWN -> sbHeightPortrait - ROTATION_LANDSCAPE, ROTATION_SEASCAPE -> sbHeightLandscape + 0, 2 -> sbHeightPortrait + 1, 3 -> sbHeightLandscape else -> 0 } synchronized(lock) { @@ -326,15 +337,22 @@ class PrivacyDotViewController @Inject constructor( } } - /** - * We won't show the dot when quick settings is showing - */ - private fun setStatusBarExpanded(expanded: Boolean) { + private fun updateStatusBarState() { synchronized(lock) { - nextViewState = nextViewState.copy(hideDotForQuickSettings = expanded) + nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs()) } } + /** + * If we are unlocked with an expanded shade, QS is showing. On keyguard, the shade is always + * expanded so we use other signals from the panel view controller to know if QS is expanded + */ + @GuardedBy("lock") + private fun isShadeInQs(): Boolean { + return (stateController.isExpanded && stateController.state == SHADE) || + (stateController.state == SHADE_LOCKED) + } + private fun scheduleUpdate() { dlog("scheduleUpdate: ") @@ -431,13 +449,20 @@ private fun dlog(s: String) { } } +private fun vlog(s: String) { + if (DEBUG_VERBOSE) { + Log.d(TAG, s) + } +} + const val TOP_LEFT = 0 const val TOP_RIGHT = 1 const val BOTTOM_RIGHT = 2 const val BOTTOM_LEFT = 3 private const val DURATION = 160L private const val TAG = "PrivacyDotViewController" -private const val DEBUG = false +private const val DEBUG = true +private const val DEBUG_VERBOSE = false private fun Int.toGravity(): Int { return when (this) { @@ -460,10 +485,10 @@ private fun Int.innerGravity(): Int { } private data class ViewState( - // don't @ me with names val systemPrivacyEventIsActive: Boolean = false, - val hideDotForQuickSettings: Boolean = false, - val statusBarExpanded: Boolean = false, + val shadeExpanded: Boolean = false, + val qsExpanded: Boolean = false, + val rotation: Int = 0, val height: Int = 0, val marginLeft: Int = 0, @@ -472,7 +497,7 @@ private data class ViewState( val designatedCorner: View? = null ) { fun shouldShowDot(): Boolean { - return systemPrivacyEventIsActive && !hideDotForQuickSettings + return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded } fun needsLayout(other: ViewState): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt new file mode 100644 index 000000000000..ce60c859e9bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -0,0 +1,277 @@ +/* + * 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.statusbar.lockscreen + +import android.app.PendingIntent +import android.app.smartspace.SmartspaceConfig +import android.app.smartspace.SmartspaceManager +import android.app.smartspace.SmartspaceSession +import android.app.smartspace.SmartspaceTarget +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.content.pm.UserInfo +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.view.View +import android.view.ViewGroup +import com.android.settingslib.Utils +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.concurrency.Execution +import com.android.systemui.util.settings.SecureSettings +import java.lang.RuntimeException +import java.util.Optional +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Controller for managing the smartspace view on the lockscreen + */ +@SysUISingleton +class LockscreenSmartspaceController @Inject constructor( + private val context: Context, + private val featureFlags: FeatureFlags, + private val smartspaceManager: SmartspaceManager, + private val activityStarter: ActivityStarter, + private val falsingManager: FalsingManager, + private val secureSettings: SecureSettings, + private val userTracker: UserTracker, + private val contentResolver: ContentResolver, + private val configurationController: ConfigurationController, + private val statusBarStateController: StatusBarStateController, + private val execution: Execution, + @Main private val uiExecutor: Executor, + @Main private val handler: Handler, + optionalPlugin: Optional<BcSmartspaceDataPlugin> +) { + private var session: SmartspaceSession? = null + private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) + private lateinit var smartspaceView: SmartspaceView + + lateinit var view: View + private set + + private var showSensitiveContentForCurrentUser = false + private var showSensitiveContentForManagedUser = false + private var managedUserHandle: UserHandle? = null + + fun isEnabled(): Boolean { + execution.assertIsMainThread() + + return featureFlags.isSmartspaceEnabled && plugin != null + } + + /** + * Constructs the smartspace view and connects it to the smartspace service. Subsequent calls + * are idempotent until [disconnect] is called. + */ + fun buildAndConnectView(parent: ViewGroup): View { + execution.assertIsMainThread() + + if (!isEnabled()) { + throw RuntimeException("Cannot build view when not enabled") + } + + buildView(parent) + connectSession() + + return view + } + + private fun buildView(parent: ViewGroup) { + if (plugin == null || this::view.isInitialized) { + return + } + + val ssView = plugin.getView(parent) + ssView.registerDataProvider(plugin) + ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter { + override fun startIntent(v: View?, i: Intent?) { + activityStarter.startActivity(i, true /* dismissShade */) + } + + override fun startPendingIntent(pi: PendingIntent?) { + activityStarter.startPendingIntentDismissingKeyguard(pi) + } + }) + ssView.setFalsingManager(falsingManager) + + this.smartspaceView = ssView + this.view = ssView as View + + updateTextColorFromWallpaper() + statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount) + } + + private fun connectSession() { + if (plugin == null || session != null) { + return + } + val session = smartspaceManager.createSmartspaceSession( + SmartspaceConfig.Builder(context, "lockscreen").build()) + session.addOnTargetsAvailableListener(uiExecutor, sessionListener) + + userTracker.addCallback(userTrackerCallback, uiExecutor) + contentResolver.registerContentObserver( + secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + true, + settingsObserver, + UserHandle.USER_ALL + ) + configurationController.addCallback(configChangeListener) + statusBarStateController.addCallback(statusBarStateListener) + + this.session = session + + reloadSmartspace() + } + + /** + * Disconnects the smartspace view from the smartspace service and cleans up any resources. + * Calling [buildAndConnectView] again will cause the same view to be reconnected to the + * service. + */ + fun disconnect() { + execution.assertIsMainThread() + + if (session == null) { + return + } + + session?.let { + it.removeOnTargetsAvailableListener(sessionListener) + it.close() + } + userTracker.removeCallback(userTrackerCallback) + contentResolver.unregisterContentObserver(settingsObserver) + configurationController.removeCallback(configChangeListener) + statusBarStateController.removeCallback(statusBarStateListener) + session = null + + plugin?.onTargetsAvailable(emptyList()) + } + + fun addListener(listener: SmartspaceTargetListener) { + execution.assertIsMainThread() + plugin?.registerListener(listener) + } + + fun removeListener(listener: SmartspaceTargetListener) { + execution.assertIsMainThread() + plugin?.unregisterListener(listener) + } + + private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> + execution.assertIsMainThread() + val filteredTargets = targets.filter(::filterSmartspaceTarget) + plugin?.onTargetsAvailable(filteredTargets) + } + + private val userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + execution.assertIsMainThread() + reloadSmartspace() + } + + override fun onProfilesChanged(profiles: List<UserInfo>) { + } + } + + private val settingsObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + execution.assertIsMainThread() + reloadSmartspace() + } + } + + private val configChangeListener = object : ConfigurationController.ConfigurationListener { + override fun onThemeChanged() { + execution.assertIsMainThread() + updateTextColorFromWallpaper() + } + } + + private val statusBarStateListener = object : StatusBarStateController.StateListener { + override fun onDozeAmountChanged(linear: Float, eased: Float) { + execution.assertIsMainThread() + smartspaceView.setDozeAmount(eased) + } + } + + private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { + return when (t.userHandle) { + userTracker.userHandle -> { + !t.isSensitive || showSensitiveContentForCurrentUser + } + managedUserHandle -> { + // Really, this should be "if this managed profile is associated with the current + // active user", but we don't have a good way to check that, so instead we cheat: + // Only the primary user can have an associated managed profile, so only show + // content for the managed profile if the primary user is active + userTracker.userHandle.identifier == UserHandle.USER_SYSTEM && + (!t.isSensitive || showSensitiveContentForManagedUser) + } + else -> { + false + } + } + } + + private fun updateTextColorFromWallpaper() { + val wallpaperTextColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor) + smartspaceView.setPrimaryTextColor(wallpaperTextColor) + } + + private fun reloadSmartspace() { + val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS + + showSensitiveContentForCurrentUser = + secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1 + + managedUserHandle = getWorkProfileUser() + val managedId = managedUserHandle?.identifier + if (managedId != null) { + showSensitiveContentForManagedUser = + secureSettings.getIntForUser(setting, 0, managedId) == 1 + } + + session?.requestSmartspaceUpdate() + } + + private fun getWorkProfileUser(): UserHandle? { + for (userInfo in userTracker.userProfiles) { + if (userInfo.isManagedProfile) { + return userInfo.userHandle + } + } + return null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index c8c0755344a4..13a8661f94dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -140,7 +140,7 @@ public class NotificationEntryManager implements private final KeyguardEnvironment mKeyguardEnvironment; private final NotificationGroupManagerLegacy mGroupManager; - private final NotificationRankingManager mRankingManager; + private final Lazy<NotificationRankingManager> mRankingManager; private final FeatureFlags mFeatureFlags; private final ForegroundServiceDismissalFeatureController mFgsFeatureController; @@ -200,7 +200,7 @@ public class NotificationEntryManager implements public NotificationEntryManager( NotificationEntryManagerLogger logger, NotificationGroupManagerLegacy groupManager, - NotificationRankingManager rankingManager, + Lazy<NotificationRankingManager> rankingManager, KeyguardEnvironment keyguardEnvironment, FeatureFlags featureFlags, Lazy<NotificationRowBinder> notificationRowBinderLazy, @@ -419,7 +419,7 @@ public class NotificationEntryManager implements mActiveNotifications.put(entry.getKey(), entry); mGroupManager.onEntryAdded(entry); - updateRankingAndSort(mRankingManager.getRankingMap(), "addEntryInternalInternal"); + updateRankingAndSort(mRankingManager.get().getRankingMap(), "addEntryInternalInternal"); } /** @@ -886,13 +886,13 @@ public class NotificationEntryManager implements /** Resorts / filters the current notification set with the current RankingMap */ public void reapplyFilterAndSort(String reason) { - updateRankingAndSort(mRankingManager.getRankingMap(), reason); + updateRankingAndSort(mRankingManager.get().getRankingMap(), reason); } /** Calls to NotificationRankingManager and updates mSortedAndFiltered */ private void updateRankingAndSort(@NonNull RankingMap rankingMap, String reason) { mSortedAndFiltered.clear(); - mSortedAndFiltered.addAll(mRankingManager.updateRanking( + mSortedAndFiltered.addAll(mRankingManager.get().updateRanking( rankingMap, mActiveNotifications.values(), reason)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java index d6356de5ea51..f40f24a935c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.notification.collection.legacy; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Notification; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; @@ -31,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.wm.shell.bubbles.Bubbles; @@ -39,10 +42,12 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.TreeSet; import javax.inject.Inject; @@ -58,13 +63,21 @@ import dagger.Lazy; public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, StateListener, GroupMembershipManager, GroupExpansionManager, Dumpable { - private static final String TAG = "NotificationGroupManager"; + private static final String TAG = "NotifGroupManager"; + private static final boolean DEBUG = StatusBar.DEBUG; + private static final boolean SPEW = StatusBar.SPEW; + /** + * The maximum amount of time (in ms) between the posting of notifications that can be + * considered part of the same update batch. + */ + private static final long POST_BATCH_MAX_AGE = 5000; private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>(); private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners = new ArraySet<>(); private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>(); private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier; private final Optional<Bubbles> mBubblesOptional; + private final EventBuffer mEventBuffer = new EventBuffer(); private int mBarState = -1; private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>(); private HeadsUpManager mHeadsUpManager; @@ -134,8 +147,14 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, * When we want to remove an entry from being tracked for grouping */ public void onEntryRemoved(NotificationEntry removed) { + if (SPEW) { + Log.d(TAG, "onEntryRemoved: entry=" + removed); + } onEntryRemovedInternal(removed, removed.getSbn()); - mIsolatedEntries.remove(removed.getKey()); + StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey()); + if (oldSbn != null) { + updateSuppression(mGroupMap.get(oldSbn.getGroupKey())); + } } /** @@ -162,6 +181,9 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, // the close future. See b/23676310 for reference. return; } + if (SPEW) { + Log.d(TAG, "onEntryRemovedInternal: entry=" + removed + " group=" + group.groupKey); + } if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) { group.children.remove(removed.getKey()); } else { @@ -182,6 +204,9 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, * Notify the group manager that a new entry was added */ public void onEntryAdded(final NotificationEntry added) { + if (SPEW) { + Log.d(TAG, "onEntryAdded: entry=" + added); + } updateIsolation(added); onEntryAddedInternal(added); } @@ -195,13 +220,16 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, String groupKey = getGroupKey(sbn); NotificationGroup group = mGroupMap.get(groupKey); if (group == null) { - group = new NotificationGroup(); + group = new NotificationGroup(groupKey); mGroupMap.put(groupKey, group); for (OnGroupChangeListener listener : mGroupChangeListeners) { listener.onGroupCreated(group, groupKey); } } + if (SPEW) { + Log.d(TAG, "onEntryAddedInternal: entry=" + added + " group=" + group.groupKey); + } if (isGroupChild) { NotificationEntry existing = group.children.get(added.getKey()); if (existing != null && existing != added) { @@ -213,9 +241,11 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, + " added removed" + added.isRowRemoved(), new Throwable()); } group.children.put(added.getKey(), added); + addToPostBatchHistory(group, added); updateSuppression(group); } else { group.summary = added; + addToPostBatchHistory(group, added); group.expanded = added.areChildrenExpanded(); updateSuppression(group); if (!group.children.isEmpty()) { @@ -231,6 +261,27 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, } } + private void addToPostBatchHistory(NotificationGroup group, @Nullable NotificationEntry entry) { + if (entry == null) { + return; + } + boolean didAdd = group.postBatchHistory.add(new PostRecord(entry)); + if (didAdd) { + trimPostBatchHistory(group.postBatchHistory); + } + } + + /** remove all history that's too old to be in the batch. */ + private void trimPostBatchHistory(@NonNull TreeSet<PostRecord> postBatchHistory) { + if (postBatchHistory.size() <= 1) { + return; + } + long batchStartTime = postBatchHistory.last().postTime - POST_BATCH_MAX_AGE; + while (!postBatchHistory.isEmpty() && postBatchHistory.first().postTime < batchStartTime) { + postBatchHistory.pollFirst(); + } + } + private void onEntryBecomingChild(NotificationEntry entry) { updateIsolation(entry); } @@ -239,6 +290,9 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, if (group == null) { return; } + NotificationEntry prevAlertOverride = group.alertOverride; + group.alertOverride = getPriorityConversationAlertOverride(group); + int childCount = 0; boolean hasBubbles = false; for (NotificationEntry entry : group.children.values()) { @@ -255,18 +309,150 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, group.suppressed = group.summary != null && !group.expanded && (childCount == 1 || (childCount == 0 - && group.summary.getSbn().getNotification().isGroupSummary() - && (hasIsolatedChildren(group) || hasBubbles))); - if (prevSuppressed != group.suppressed) { - for (OnGroupChangeListener listener : mGroupChangeListeners) { - if (!mIsUpdatingUnchangedGroup) { - listener.onGroupSuppressionChanged(group, group.suppressed); - listener.onGroupsChanged(); + && group.summary.getSbn().getNotification().isGroupSummary() + && (hasIsolatedChildren(group) || hasBubbles))); + + boolean alertOverrideChanged = prevAlertOverride != group.alertOverride; + boolean suppressionChanged = prevSuppressed != group.suppressed; + if (alertOverrideChanged || suppressionChanged) { + if (DEBUG && alertOverrideChanged) { + Log.d(TAG, "updateSuppression: alertOverride was=" + prevAlertOverride + + " now=" + group.alertOverride + " group:\n" + group); + } + if (DEBUG && suppressionChanged) { + Log.d(TAG, + "updateSuppression: suppressed changed to " + group.suppressed + + " group:\n" + group); + } + if (!mIsUpdatingUnchangedGroup) { + if (alertOverrideChanged) { + mEventBuffer.notifyAlertOverrideChanged(group, prevAlertOverride); + } + if (suppressionChanged) { + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupSuppressionChanged(group, group.suppressed); + } + } + mEventBuffer.notifyGroupsChanged(); + } else { + if (DEBUG) { + Log.d(TAG, group + " did not notify listeners of above change(s)"); } } } } + /** + * Finds the isolated logical child of this group which is should be alerted instead. + * + * Notifications from priority conversations are isolated from their groups to make them more + * prominent, however apps may post these with a GroupAlertBehavior that has the group receiving + * the alert. This would lead to the group alerting even though the conversation that was + * updated was not actually a part of that group. This method finds the best priority + * conversation in this situation, if there is one, so they can be set as the alertOverride of + * the group. + * + * @param group the group to check + * @return the entry which should receive the alert instead of the group, if any. + */ + @Nullable + private NotificationEntry getPriorityConversationAlertOverride(NotificationGroup group) { + // GOAL: if there is a priority child which wouldn't alert based on its groupAlertBehavior, + // but which should be alerting (because priority conversations are isolated), find it. + if (group == null || group.summary == null) { + if (SPEW) { + Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary"); + } + return null; + } + if (isIsolated(group.summary.getKey())) { + if (SPEW) { + Log.d(TAG, "getPriorityConversationAlertOverride: isolated group"); + } + return null; + } + + // Precondiions: + // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY + // * Only necessary when at least one notification in the group is on a priority channel + if (group.summary.getSbn().getNotification().getGroupAlertBehavior() + != Notification.GROUP_ALERT_SUMMARY) { + if (SPEW) { + Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY"); + } + return null; + } + + // Get the important children first, copy the keys for the final importance check, + // then add the non-isolated children to the map for unified lookup. + HashMap<String, NotificationEntry> children = getImportantConversations(group); + if (children == null || children.isEmpty()) { + if (SPEW) { + Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations"); + } + return null; + } + HashSet<String> importantChildKeys = new HashSet<>(children.keySet()); + children.putAll(group.children); + + // Ensure all children have GROUP_ALERT_SUMMARY + for (NotificationEntry child : children.values()) { + if (child.getSbn().getNotification().getGroupAlertBehavior() + != Notification.GROUP_ALERT_SUMMARY) { + if (SPEW) { + Log.d(TAG, "getPriorityConversationAlertOverride: " + + "child != GROUP_ALERT_SUMMARY"); + } + return null; + } + } + + // Create a merged post history from all the children + TreeSet<PostRecord> combinedHistory = new TreeSet<>(group.postBatchHistory); + for (String importantChildKey : importantChildKeys) { + NotificationGroup importantChildGroup = mGroupMap.get(importantChildKey); + combinedHistory.addAll(importantChildGroup.postBatchHistory); + } + trimPostBatchHistory(combinedHistory); + + // This is a streamlined implementation of the following idea: + // * From the subset of notifications in the latest 'batch' of updates. A batch is: + // * Notifs posted less than POST_BATCH_MAX_AGE before the most recently posted. + // * Only including notifs newer than the second-to-last post of any notification. + // * Find the newest child in the batch -- the with the largest 'when' value. + // * If the newest child is a priority conversation, set that as the override. + HashSet<String> batchKeys = new HashSet<>(); + long newestChildWhen = -1; + NotificationEntry newestChild = null; + // Iterate backwards through the post history, tracking the child with the smallest sort key + for (PostRecord record : combinedHistory.descendingSet()) { + if (batchKeys.contains(record.key)) { + // Once you see a notification again, the batch has ended + break; + } + batchKeys.add(record.key); + NotificationEntry child = children.get(record.key); + if (child != null) { + long childWhen = child.getSbn().getNotification().when; + if (newestChild == null || childWhen > newestChildWhen) { + newestChildWhen = childWhen; + newestChild = child; + } + } + } + if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) { + if (SPEW) { + Log.d(TAG, "getPriorityConversationAlertOverride: result=" + newestChild); + } + return newestChild; + } + if (SPEW) { + Log.d(TAG, "getPriorityConversationAlertOverride: result=null, newestChild=" + + newestChild); + } + return null; + } + private boolean hasIsolatedChildren(NotificationGroup group) { return getNumberOfIsolatedChildren(group.summary.getSbn().getGroupKey()) != 0; } @@ -281,12 +467,33 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, return count; } + @Nullable + private HashMap<String, NotificationEntry> getImportantConversations(NotificationGroup group) { + String groupKey = group.summary.getSbn().getGroupKey(); + HashMap<String, NotificationEntry> result = null; + for (StatusBarNotification sbn : mIsolatedEntries.values()) { + if (sbn.getGroupKey().equals(groupKey)) { + NotificationEntry entry = mGroupMap.get(sbn.getKey()).summary; + if (isImportantConversation(entry)) { + if (result == null) { + result = new HashMap<>(); + } + result.put(sbn.getKey(), entry); + } + } + } + return result; + } + /** * Update an entry's group information * @param entry notification entry to update * @param oldNotification previous notification info before this update */ public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) { + if (SPEW) { + Log.d(TAG, "onEntryUpdated: entry=" + entry); + } onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(), oldNotification.getNotification().isGroupSummary()); } @@ -325,7 +532,17 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, * Whether the given notification is the summary of a group that is being suppressed */ public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) { - return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary(); + return sbn.getNotification().isGroupSummary() && isGroupSuppressed(getGroupKey(sbn)); + } + + /** + * If the given notification is a summary, get the group for it. + */ + public NotificationGroup getGroupForSummary(StatusBarNotification sbn) { + if (sbn.getNotification().isGroupSummary()) { + return mGroupMap.get(getGroupKey(sbn)); + } + return null; } private boolean isOnlyChild(StatusBarNotification sbn) { @@ -545,9 +762,7 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) { return false; } - int peopleNotificationType = - mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry); - if (peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON) { + if (isImportantConversation(entry)) { return true; } if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) { @@ -560,18 +775,25 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, || isGroupNotFullyVisible(notificationGroup)); } + private boolean isImportantConversation(NotificationEntry entry) { + int peopleNotificationType = + mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry); + return peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON; + } + /** * Isolate a notification from its group so that it visually shows as its own group. * * @param entry the notification to isolate */ private void isolateNotification(NotificationEntry entry) { - StatusBarNotification sbn = entry.getSbn(); - + if (SPEW) { + Log.d(TAG, "isolateNotification: entry=" + entry); + } // We will be isolated now, so lets update the groups onEntryRemovedInternal(entry, entry.getSbn()); - mIsolatedEntries.put(sbn.getKey(), sbn); + mIsolatedEntries.put(entry.getKey(), entry.getSbn()); onEntryAddedInternal(entry); // We also need to update the suppression of the old group, because this call comes @@ -588,6 +810,14 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, * Update the isolation of an entry, splitting it from the group. */ public void updateIsolation(NotificationEntry entry) { + // We need to buffer a few events because we do isolation changes in 3 steps: + // removeInternal, update mIsolatedEntries, addInternal. This means that often the + // alertOverride will update on the removal, however processing the event in that case can + // cause problems because the mIsolatedEntries map is not in its final state, so the event + // listener may be unable to correctly determine the true state of the group. By delaying + // the alertOverride change until after the add phase, we can ensure that listeners only + // have to handle a consistent state. + mEventBuffer.startBuffering(); boolean isIsolated = isIsolated(entry.getSbn().getKey()); if (shouldIsolate(entry)) { if (!isIsolated) { @@ -596,6 +826,7 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, } else if (isIsolated) { stopIsolatingNotification(entry); } + mEventBuffer.flushAndStopBuffering(); } /** @@ -604,15 +835,15 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, * @param entry the notification to un-isolate */ private void stopIsolatingNotification(NotificationEntry entry) { - StatusBarNotification sbn = entry.getSbn(); - if (isIsolated(sbn.getKey())) { - // not isolated anymore, we need to update the groups - onEntryRemovedInternal(entry, entry.getSbn()); - mIsolatedEntries.remove(sbn.getKey()); - onEntryAddedInternal(entry); - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupsChanged(); - } + if (SPEW) { + Log.d(TAG, "stopIsolatingNotification: entry=" + entry); + } + // not isolated anymore, we need to update the groups + onEntryRemovedInternal(entry, entry.getSbn()); + mIsolatedEntries.remove(entry.getKey()); + onEntryAddedInternal(entry); + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupsChanged(); } } @@ -648,33 +879,154 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, } /** + * A record of a notification being posted, containing the time of the post and the key of the + * notification entry. These are stored in a TreeSet by the NotificationGroup and used to + * calculate a batch of notifications. + */ + public static class PostRecord implements Comparable<PostRecord> { + public final long postTime; + public final String key; + + /** constructs a record containing the post time and key from the notification entry */ + public PostRecord(@NonNull NotificationEntry entry) { + this.postTime = entry.getSbn().getPostTime(); + this.key = entry.getKey(); + } + + @Override + public int compareTo(PostRecord o) { + int postTimeComparison = Long.compare(this.postTime, o.postTime); + return postTimeComparison == 0 + ? String.CASE_INSENSITIVE_ORDER.compare(this.key, o.key) + : postTimeComparison; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PostRecord that = (PostRecord) o; + return postTime == that.postTime && key.equals(that.key); + } + + @Override + public int hashCode() { + return Objects.hash(postTime, key); + } + } + + /** * Represents a notification group in the notification shade. */ public static class NotificationGroup { + public final String groupKey; public final HashMap<String, NotificationEntry> children = new HashMap<>(); + public final TreeSet<PostRecord> postBatchHistory = new TreeSet<>(); public NotificationEntry summary; public boolean expanded; /** * Is this notification group suppressed, i.e its summary is hidden */ public boolean suppressed; + /** + * The child (which is isolated from this group) to which the alert should be transferred, + * due to priority conversations. + */ + public NotificationEntry alertOverride; + + NotificationGroup(String groupKey) { + this.groupKey = groupKey; + } @Override public String toString() { - String result = " summary:\n " - + (summary != null ? summary.getSbn() : "null") - + (summary != null && summary.getDebugThrowable() != null - ? Log.getStackTraceString(summary.getDebugThrowable()) - : ""); - result += "\n children size: " + children.size(); + StringBuilder sb = new StringBuilder(); + sb.append(" groupKey: ").append(groupKey); + sb.append("\n summary:"); + appendEntry(sb, summary); + sb.append("\n children size: ").append(children.size()); for (NotificationEntry child : children.values()) { - result += "\n " + child.getSbn() - + (child.getDebugThrowable() != null - ? Log.getStackTraceString(child.getDebugThrowable()) - : ""); + appendEntry(sb, child); + } + sb.append("\n alertOverride:"); + appendEntry(sb, alertOverride); + sb.append("\n summary suppressed: ").append(suppressed); + return sb.toString(); + } + + private void appendEntry(StringBuilder sb, NotificationEntry entry) { + sb.append("\n ").append(entry != null ? entry.getSbn() : "null"); + if (entry != null && entry.getDebugThrowable() != null) { + sb.append(Log.getStackTraceString(entry.getDebugThrowable())); + } + } + } + + /** + * This class is a toggleable buffer for a subset of events of {@link OnGroupChangeListener}. + * When buffering, instead of notifying the listeners it will set internal state that will allow + * it to notify listeners of those events later + */ + private class EventBuffer { + private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>(); + private boolean mIsBuffering = false; + private boolean mDidGroupsChange = false; + + void notifyAlertOverrideChanged(NotificationGroup group, + NotificationEntry oldAlertOverride) { + if (mIsBuffering) { + // The value in this map is the override before the event. If there is an entry + // already in the map, then we are effectively coalescing two events, which means + // we need to preserve the original initial value. + mOldAlertOverrideByGroup.putIfAbsent(group.groupKey, oldAlertOverride); + } else { + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupAlertOverrideChanged(group, oldAlertOverride, + group.alertOverride); + } + } + } + + void notifyGroupsChanged() { + if (mIsBuffering) { + mDidGroupsChange = true; + } else { + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupsChanged(); + } + } + } + + void startBuffering() { + mIsBuffering = true; + } + + void flushAndStopBuffering() { + // stop buffering so that we can call our own helpers + mIsBuffering = false; + // alert all group alert override changes for groups that were not removed + for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) { + NotificationGroup group = mGroupMap.get(entry.getKey()); + if (group == null) { + // The group can be null if this alertOverride changed before the group was + // permanently removed, meaning that there's no guarantee that listeners will + // that field clear. + continue; + } + NotificationEntry oldAlertOverride = entry.getValue(); + if (group.alertOverride == oldAlertOverride) { + // If the final alertOverride equals the initial, it means we coalesced two + // events which undid the change, so we can drop it entirely. + continue; + } + notifyAlertOverrideChanged(group, oldAlertOverride); + } + mOldAlertOverrideByGroup.clear(); + // alert that groups changed + if (mDidGroupsChange) { + notifyGroupsChanged(); + mDidGroupsChange = false; } - result += "\n summary suppressed: " + suppressed; - return result; } } @@ -714,6 +1066,18 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, boolean suppressed) {} /** + * The alert override of a group has changed. + * + * @param group the group that has changed + * @param oldAlertOverride the previous notification to which the group's alerts were sent + * @param newAlertOverride the notification to which the group's alerts should now be sent + */ + default void onGroupAlertOverrideChanged( + NotificationGroup group, + @Nullable NotificationEntry oldAlertOverride, + @Nullable NotificationEntry newAlertOverride) {} + + /** * A group of children just received a summary notification and should therefore become * children of it. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index e2a37f647bf5..89bb65278dce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -80,8 +80,6 @@ import com.android.systemui.wmshell.BubblesManager; import java.util.Optional; import java.util.concurrent.Executor; -import javax.inject.Provider; - import dagger.Binds; import dagger.Lazy; import dagger.Module; @@ -102,7 +100,7 @@ public interface NotificationsModule { static NotificationEntryManager provideNotificationEntryManager( NotificationEntryManagerLogger logger, NotificationGroupManagerLegacy groupManager, - NotificationRankingManager rankingManager, + Lazy<NotificationRankingManager> rankingManager, NotificationEntryManager.KeyguardEnvironment keyguardEnvironment, FeatureFlags featureFlags, Lazy<NotificationRowBinder> notificationRowBinderLazy, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 120f9732f555..3b64d48f4df9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -664,7 +664,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mDebugPaint.setColor(Color.CYAN); canvas.drawLine(0, y, getWidth(), y, mDebugPaint); - y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); + y = (int) (mAmbientState.getStackY() + mSidePaddings + mAmbientState.getStackHeight()); mDebugPaint.setColor(Color.BLUE); canvas.drawLine(0, y, getWidth(), y, mDebugPaint); } @@ -1148,12 +1148,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mOnStackYChanged != null) { mOnStackYChanged.run(); } - - final float stackEndHeight = getHeight() - getEmptyBottomMargin() - mTopPadding; - mAmbientState.setStackEndHeight(stackEndHeight); - mAmbientState.setStackHeight( - MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION, - stackEndHeight, fraction)); + if (mQsExpansionFraction <= 0) { + final float stackEndHeight = Math.max(0f, + getHeight() - getEmptyBottomMargin() - stackY - mSidePaddings); + mAmbientState.setStackEndHeight(stackEndHeight); + mAmbientState.setStackHeight( + MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION, + stackEndHeight, fraction)); + } } void setOnStackYChanged(Runnable onStackYChanged) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index d94d030f326e..b2d39a952fe2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -58,6 +58,7 @@ public class StackScrollAlgorithm { private int mStatusBarHeight; private float mHeadsUpInset; private int mPinnedZTranslationExtra; + private float mNotificationScrimPadding; public StackScrollAlgorithm( Context context, @@ -82,6 +83,7 @@ public class StackScrollAlgorithm { mPinnedZTranslationExtra = res.getDimensionPixelSize( R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); + mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings); } /** @@ -258,6 +260,9 @@ public class StackScrollAlgorithm { // expanded. Consider updating these states in updateContentView instead so that we don't // have to recalculate in every frame. float currentY = -scrollY; + if (!ambientState.isOnKeyguard()) { + currentY += mNotificationScrimPadding; + } float previousY = 0; state.firstViewInShelf = null; state.viewHeightBeforeShelf = -1; @@ -318,6 +323,9 @@ public class StackScrollAlgorithm { AmbientState ambientState) { // The y coordinate of the current child. float currentYPosition = -algorithmState.scrollY; + if (!ambientState.isOnKeyguard()) { + currentYPosition += mNotificationScrimPadding; + } int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 2b51b56062bb..c5a155efdc86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -23,6 +23,7 @@ import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; import android.animation.ValueAnimator; import android.annotation.Nullable; @@ -325,11 +326,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue // Show the ongoing call chip only if there is an ongoing call *and* notification icons // are allowed. (The ongoing call chip occupies the same area as the notification icons, // so if the icons are disabled then the call chip should be, too.) - if (hasOngoingCall && !disableNotifications) { + boolean showOngoingCallChip = hasOngoingCall && !disableNotifications; + if (showOngoingCallChip) { showOngoingCallChip(animate); } else { hideOngoingCallChip(animate); } + mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip); } private boolean shouldHideNotificationIcons() { @@ -348,7 +351,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private void showSystemIconArea(boolean animate) { // Only show the system icon area if we are not currently animating - if (mAnimationScheduler.getAnimationState() == IDLE) { + int state = mAnimationScheduler.getAnimationState(); + if (state == IDLE || state == SHOWING_PERSISTENT_DOT) { animateShow(mSystemIconArea, animate); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 0bcdcb96e39a..95098bd1151b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -936,7 +936,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } GetWalletCardsRequest request = new GetWalletCardsRequest(1 /* cardWidth */, 1 /* cardHeight */, - 1 /* iconSizePx */, 2 /* maxCards */); + 1 /* iconSizePx */, 1 /* maxCards */); mQuickAccessWalletClient.getWalletCards(mUiExecutor, request, mCardRetriever); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 3181f520dca2..9787a9446019 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -22,12 +22,12 @@ import android.app.Notification; import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import android.util.Log; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -41,17 +41,21 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** * A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy} * and {@link HeadsUpManager}. In particular, this class deals with keeping - * the correct notification in a group alerting based off the group suppression. + * the correct notification in a group alerting based off the group suppression and alertOverride. */ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener, StateListener { private static final long ALERT_TRANSFER_TIMEOUT = 300; + private static final String TAG = "NotifGroupAlertTransfer"; + private static final boolean DEBUG = StatusBar.DEBUG; + private static final boolean SPEW = StatusBar.SPEW; /** * The list of entries containing group alert metadata for each group. Keyed by group key. @@ -142,41 +146,98 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) { - if (suppressed) { - if (mHeadsUpManager.isAlerting(group.summary.getKey())) { - handleSuppressedSummaryAlerted(group.summary, mHeadsUpManager); + if (DEBUG) { + Log.d(TAG, "!! onGroupSuppressionChanged: group.summary=" + group.summary + + " suppressed=" + suppressed); + } + NotificationEntry oldAlertOverride = group.alertOverride; + onGroupChanged(group, oldAlertOverride); + } + + @Override + public void onGroupAlertOverrideChanged(NotificationGroup group, + @Nullable NotificationEntry oldAlertOverride, + @Nullable NotificationEntry newAlertOverride) { + if (DEBUG) { + Log.d(TAG, "!! onGroupAlertOverrideChanged: group.summary=" + group.summary + + " oldAlertOverride=" + oldAlertOverride + + " newAlertOverride=" + newAlertOverride); + } + onGroupChanged(group, oldAlertOverride); + } + }; + + /** + * Called when either the suppressed or alertOverride fields of the group changed + * + * @param group the group which changed + * @param oldAlertOverride the previous value of group.alertOverride + */ + private void onGroupChanged(NotificationGroup group, + NotificationEntry oldAlertOverride) { + // Group summary can be null if we are no longer suppressed because the summary was + // removed. In that case, we don't need to alert the summary. + if (group.summary == null) { + if (DEBUG) { + Log.d(TAG, "onGroupChanged: summary is null"); + } + return; + } + if (group.suppressed || group.alertOverride != null) { + checkForForwardAlertTransfer(group.summary, oldAlertOverride); + } else { + if (DEBUG) { + Log.d(TAG, "onGroupChanged: maybe transfer back"); + } + GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey( + group.summary.getSbn())); + // Group is no longer suppressed or overridden. + // We should check if we need to transfer the alert back to the summary. + if (groupAlertEntry.mAlertSummaryOnNextAddition) { + if (!mHeadsUpManager.isAlerting(group.summary.getKey())) { + alertNotificationWhenPossible(group.summary); } + groupAlertEntry.mAlertSummaryOnNextAddition = false; } else { - // Group summary can be null if we are no longer suppressed because the summary was - // removed. In that case, we don't need to alert the summary. - if (group.summary == null) { - return; - } - GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey( - group.summary.getSbn())); - // Group is no longer suppressed. We should check if we need to transfer the alert - // back to the summary now that it's no longer suppressed. - if (groupAlertEntry.mAlertSummaryOnNextAddition) { - if (!mHeadsUpManager.isAlerting(group.summary.getKey())) { - alertNotificationWhenPossible(group.summary, mHeadsUpManager); - } - groupAlertEntry.mAlertSummaryOnNextAddition = false; - } else { - checkShouldTransferBack(groupAlertEntry); - } + checkShouldTransferBack(groupAlertEntry); } } - }; + } @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager); + if (DEBUG) { + Log.d(TAG, "!! onHeadsUpStateChanged: entry=" + entry + " isHeadsUp=" + isHeadsUp); + } + if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) { + // a group summary is alerting; trigger the forward transfer checks + checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null); + } } - private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting, - AlertingNotificationManager alertManager) { - if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.getSbn())) { - handleSuppressedSummaryAlerted(entry, alertManager); + /** + * Handles changes in a group's suppression or alertOverride, but where at least one of those + * conditions is still true (either the group is suppressed, the group has an alertOverride, + * or both). The method determined which kind of child needs to receive the alert, finds the + * entry currently alerting, and makes the transfer. + * + * Internally, this is handled with two main cases: the override needs the alert, or there is + * no override but the summary is suppressed (so an isolated child needs the alert). + * + * @param summary the notification entry of the summary of the logical group. + * @param oldAlertOverride the former value of group.alertOverride, before whatever event + * required us to check for for a transfer condition. + */ + private void checkForForwardAlertTransfer(NotificationEntry summary, + NotificationEntry oldAlertOverride) { + if (DEBUG) { + Log.d(TAG, "checkForForwardAlertTransfer: enter"); + } + NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn()); + if (group != null && group.alertOverride != null) { + handleOverriddenSummaryAlerted(summary); + } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) { + handleSuppressedSummaryAlerted(summary, oldAlertOverride); } } @@ -186,9 +247,16 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis // see as early as we can if we need to abort a transfer. @Override public void onPendingEntryAdded(NotificationEntry entry) { + if (DEBUG) { + Log.d(TAG, "!! onPendingEntryAdded: entry=" + entry); + } String groupKey = mGroupManager.getGroupKey(entry.getSbn()); GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); - if (groupAlertEntry != null) { + if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) { + // new pending group entries require us to transfer back from the child to the + // group, but alertOverrides are only present in very limited circumstances, so + // while it's possible the group should ALSO alert, the previous detection which set + // this alertOverride won't be invalidated by this notification added to this group. checkShouldTransferBack(groupAlertEntry); } } @@ -262,43 +330,128 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } /** - * Handles the scenario where a summary that has been suppressed is alerted. A suppressed + * Handles the scenario where a summary that has been suppressed is itself, or has a former + * alertOverride (in the form of an isolated logical child) which was alerted. A suppressed * summary should for all intents and purposes be invisible to the user and as a result should * not alert. When this is the case, it is our responsibility to pass the alert to the * appropriate child which will be the representative notification alerting for the group. * - * @param summary the summary that is suppressed and alerting - * @param alertManager the alert manager that manages the alerting summary + * @param summary the summary that is suppressed and (potentially) alerting + * @param oldAlertOverride the alertOverride before whatever event triggered this method. If + * the alert override was removed, this will be the entry that should + * be transferred back from. */ private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary, - @NonNull AlertingNotificationManager alertManager) { - StatusBarNotification sbn = summary.getSbn(); + NotificationEntry oldAlertOverride) { + if (DEBUG) { + Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + summary); + } GroupAlertEntry groupAlertEntry = - mGroupAlertEntries.get(mGroupManager.getGroupKey(sbn)); + mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn())); + if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn()) - || !alertManager.isAlerting(sbn.getKey()) || groupAlertEntry == null) { + if (DEBUG) { + Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state"); + } + return; + } + boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey()); + boolean priorityIsAlerting = oldAlertOverride != null + && mHeadsUpManager.isAlerting(oldAlertOverride.getKey()); + if (!summaryIsAlerting && !priorityIsAlerting) { + if (DEBUG) { + Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting"); + } return; } if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) { // New children will actually be added to this group, let's not transfer the alert. + if (DEBUG) { + Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations"); + } return; } NotificationEntry child = mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next(); - if (child != null) { - if (child.getRow().keepInParent() - || child.isRowRemoved() - || child.isRowDismissed()) { - // The notification is actually already removed. No need to alert it. - return; + if (summaryIsAlerting) { + if (DEBUG) { + Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child"); } - if (!alertManager.isAlerting(child.getKey()) && onlySummaryAlerts(summary)) { - groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime(); + tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry); + return; + } + // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure + // it's not too late to transfer back, then transfer the alert from the oldAlertOverride to + // the isolated child which should receive the alert. + if (!canStillTransferBack(groupAlertEntry)) { + if (DEBUG) { + Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late"); + } + return; + } + + if (DEBUG) { + Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child"); + } + tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry); + } + + /** + * Checks for and handles the scenario where the given entry is the summary of a group which + * has an alertOverride, and either the summary itself or one of its logical isolated children + * is currently alerting (which happens if the summary is suppressed). + */ + private void handleOverriddenSummaryAlerted(NotificationEntry summary) { + if (DEBUG) { + Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + summary); + } + GroupAlertEntry groupAlertEntry = + mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn())); + NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn()); + if (group == null || group.alertOverride == null || groupAlertEntry == null) { + if (DEBUG) { + Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state"); + } + return; + } + boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey()); + if (summaryIsAlerting) { + if (DEBUG) { + Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override"); + } + tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry); + return; + } + // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure + // it's not too late to transfer back, then remove the alert from any of the logical + // children, and if one of them was alerting, we can alert the override. + if (!canStillTransferBack(groupAlertEntry)) { + if (DEBUG) { + Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late"); + } + return; + } + List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn()); + if (children == null) { + if (DEBUG) { + Log.d(TAG, "handleOverriddenSummaryAlerted: no children"); + } + return; + } + children.remove(group.alertOverride); // do not release the alert on our desired destination + boolean releasedChild = releaseChildAlerts(children); + if (releasedChild) { + if (DEBUG) { + Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override"); + } + tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry); + } else { + if (DEBUG) { + Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released"); } - transferAlertState(summary, child, alertManager); } } @@ -307,14 +460,37 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * immediately to have the incorrect one up as short as possible. The second should alert * when possible. * + * @param summary entry of the summary * @param fromEntry entry to transfer alert from * @param toEntry entry to transfer to - * @param alertManager alert manager for the alert type */ - private void transferAlertState(@NonNull NotificationEntry fromEntry, @NonNull NotificationEntry toEntry, - @NonNull AlertingNotificationManager alertManager) { - alertManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */); - alertNotificationWhenPossible(toEntry, alertManager); + private void tryTransferAlertState( + NotificationEntry summary, + NotificationEntry fromEntry, + NotificationEntry toEntry, + GroupAlertEntry groupAlertEntry) { + if (toEntry != null) { + if (toEntry.getRow().keepInParent() + || toEntry.isRowRemoved() + || toEntry.isRowDismissed()) { + // The notification is actually already removed. No need to alert it. + return; + } + if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) { + groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime(); + } + if (DEBUG) { + Log.d(TAG, "transferAlertState: fromEntry=" + fromEntry + " toEntry=" + toEntry); + } + transferAlertState(fromEntry, toEntry); + } + } + private void transferAlertState(@Nullable NotificationEntry fromEntry, + @NonNull NotificationEntry toEntry) { + if (fromEntry != null) { + mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */); + } + alertNotificationWhenPossible(toEntry); } /** @@ -326,11 +502,13 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * more children are coming. Thus, if a child is added within a certain timeframe after we * transfer, we back out and alert the summary again. * + * An alert can only transfer back within a small window of time after a transfer away from the + * summary to a child happened. + * * @param groupAlertEntry group alert entry to check */ private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) { - if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime - < ALERT_TRANSFER_TIMEOUT) { + if (canStillTransferBack(groupAlertEntry)) { NotificationEntry summary = groupAlertEntry.mGroup.summary; if (!onlySummaryAlerts(summary)) { @@ -338,30 +516,17 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren( summary.getSbn()); - int numChildren = children.size(); + int numActiveChildren = children.size(); int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup); - numChildren += numPendingChildren; + int numChildren = numActiveChildren + numPendingChildren; if (numChildren <= 1) { return; } - boolean releasedChild = false; - for (int i = 0; i < children.size(); i++) { - NotificationEntry entry = children.get(i); - if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) { - releasedChild = true; - mHeadsUpManager.removeNotification( - entry.getKey(), true /* releaseImmediately */); - } - if (mPendingAlerts.containsKey(entry.getKey())) { - // This is the child that would've been removed if it was inflated. - releasedChild = true; - mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true; - } - } + boolean releasedChild = releaseChildAlerts(children); if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) { - boolean notifyImmediately = (numChildren - numPendingChildren) > 1; + boolean notifyImmediately = numActiveChildren > 1; if (notifyImmediately) { - alertNotificationWhenPossible(summary, mHeadsUpManager); + alertNotificationWhenPossible(summary); } else { // Should wait until the pending child inflates before alerting. groupAlertEntry.mAlertSummaryOnNextAddition = true; @@ -371,25 +536,61 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } } + private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) { + return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime + < ALERT_TRANSFER_TIMEOUT; + } + + private boolean releaseChildAlerts(List<NotificationEntry> children) { + boolean releasedChild = false; + if (SPEW) { + Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size()); + } + for (int i = 0; i < children.size(); i++) { + NotificationEntry entry = children.get(i); + if (SPEW) { + Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry + + " onlySummaryAlerts=" + onlySummaryAlerts(entry) + + " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey()) + + " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey())); + } + if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) { + releasedChild = true; + mHeadsUpManager.removeNotification( + entry.getKey(), true /* releaseImmediately */); + } + if (mPendingAlerts.containsKey(entry.getKey())) { + // This is the child that would've been removed if it was inflated. + releasedChild = true; + mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true; + } + } + if (SPEW) { + Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild); + } + return releasedChild; + } + /** * Tries to alert the notification. If its content view is not inflated, we inflate and continue * when the entry finishes inflating the view. * * @param entry entry to show - * @param alertManager alert manager for the alert type */ - private void alertNotificationWhenPossible(@NonNull NotificationEntry entry, - @NonNull AlertingNotificationManager alertManager) { - @InflationFlag int contentFlag = alertManager.getContentFlag(); + private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) { + @InflationFlag int contentFlag = mHeadsUpManager.getContentFlag(); final RowContentBindParams params = mRowContentBindStage.getStageParams(entry); if ((params.getContentViews() & contentFlag) == 0) { + if (DEBUG) { + Log.d(TAG, "alertNotificationWhenPossible: async requestRebind entry=" + entry); + } mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry)); params.requireContentViews(contentFlag); mRowContentBindStage.requestRebind(entry, en -> { PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey()); if (alertInfo != null) { if (alertInfo.isStillValid()) { - alertNotificationWhenPossible(entry, mHeadsUpManager); + alertNotificationWhenPossible(entry); } else { // The transfer is no longer valid. Free the content. mRowContentBindStage.getStageParams(entry).markContentViewsFreeable( @@ -400,10 +601,16 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis }); return; } - if (alertManager.isAlerting(entry.getKey())) { - alertManager.updateNotification(entry.getKey(), true /* alert */); + if (mHeadsUpManager.isAlerting(entry.getKey())) { + if (DEBUG) { + Log.d(TAG, "alertNotificationWhenPossible: continue alerting entry=" + entry); + } + mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */); } else { - alertManager.showNotification(entry); + if (DEBUG) { + Log.d(TAG, "alertNotificationWhenPossible: start alerting entry=" + entry); + } + mHeadsUpManager.showNotification(entry); } } 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 82bff2c8bf82..632a01b5b33f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -124,6 +124,7 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; @@ -308,6 +309,7 @@ public class NotificationPanelViewController extends PanelViewController { private final QSDetailDisplayer mQSDetailDisplayer; private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; + private final PrivacyDotViewController mPrivacyDotViewController; // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card. // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications @@ -607,6 +609,7 @@ public class NotificationPanelViewController extends PanelViewController { FeatureFlags featureFlags, QuickAccessWalletClient quickAccessWalletClient, KeyguardMediaController keyguardMediaController, + PrivacyDotViewController privacyDotViewController, @Main Executor uiExecutor, EmergencyButtonController.Factory emergencyButtonControllerFactory) { super(view, falsingManager, dozeLog, keyguardStateController, @@ -616,6 +619,7 @@ public class NotificationPanelViewController extends PanelViewController { mView = view; mVibratorHelper = vibratorHelper; mKeyguardMediaController = keyguardMediaController; + mPrivacyDotViewController = privacyDotViewController; mMetricsLogger = metricsLogger; mActivityManager = activityManager; mConfigurationController = configurationController; @@ -1906,6 +1910,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBypassController.setQSExpanded(expanded); mStatusBarKeyguardViewManager.setQsExpanded(expanded); mLockIconViewController.setQsExpanded(expanded); + mPrivacyDotViewController.setQsExpanded(expanded); } } @@ -2078,14 +2083,13 @@ public class NotificationPanelViewController extends PanelViewController { final int qsPanelBottomY = calculateQsBottomPosition(getQsExpansionFraction()); final boolean visible = (getQsExpansionFraction() > 0 || qsPanelBottomY > 0) && !mShouldUseSplitNotificationShade; - final float notificationTop = mAmbientState.getStackY() - - mNotificationScrimPadding - - mAmbientState.getScrollY(); + final float notificationTop = mAmbientState.getStackY() - mAmbientState.getScrollY(); setQsExpansionEnabled(mAmbientState.getScrollY() == 0); int radius = mScrimCornerRadius; if (!mShouldUseSplitNotificationShade) { - top = (int) Math.min(qsPanelBottomY, notificationTop); + top = (int) (isOnKeyguard() ? Math.min(qsPanelBottomY, notificationTop) + : notificationTop); bottom = getView().getBottom(); left = getView().getLeft(); right = getView().getRight(); @@ -2585,16 +2589,19 @@ public class NotificationPanelViewController extends PanelViewController { // Small parallax as we pull down and clip QS startHeight = -mQsExpansionHeight * 0.2f; } - if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard() - && mNotificationStackScrollLayoutController.isPulseExpanding()) { - if (!mPulseExpansionHandler.isExpanding() - && !mPulseExpansionHandler.getLeavingLockscreen()) { - // If we aborted the expansion we need to make sure the header doesn't reappear - // again after the header has animated away - appearAmount = 0; + if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) { + if (mNotificationStackScrollLayoutController.isPulseExpanding()) { + if (!mPulseExpansionHandler.isExpanding() + && !mPulseExpansionHandler.getLeavingLockscreen()) { + // If we aborted the expansion we need to make sure the header doesn't reappear + // again after the header has animated away + appearAmount = 0; + } else { + appearAmount = mNotificationStackScrollLayoutController + .calculateAppearFractionBypass(); + } } else { - appearAmount = mNotificationStackScrollLayoutController - .calculateAppearFractionBypass(); + appearAmount = 0.0f; } startHeight = -mQs.getQsMinExpansionHeight(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java index df5bdb83b4da..4f67a391d83e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -97,13 +97,13 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity); mIconController = iconController; + mCarrierConfigTracker = carrierConfigTracker; mNetworkController = Dependency.get(NetworkController.class); mSecurityController = Dependency.get(SecurityController.class); Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_HIDE_LIST); mNetworkController.addCallback(this); mSecurityController.addCallback(this); - mCarrierConfigTracker = carrierConfigTracker; } public void destroy() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt index 1fe77fd441bc..6e27caec9365 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt @@ -56,6 +56,7 @@ class OngoingCallChronometer @JvmOverloads constructor( // call starts. minimumTextWidth = 0 shouldHideText = false + visibility = VISIBLE super.setBase(base) } @@ -76,6 +77,9 @@ class OngoingCallChronometer @JvmOverloads constructor( if (desiredTextWidth > enforcedTextWidth) { shouldHideText = true + // Changing visibility ensures that the content description is not read aloud when the + // time isn't displayed. + visibility = GONE setMeasuredDimension(0, 0) } else { // It's possible that the current text could fit in a smaller width, but we don't want diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 6d1df5b5fa19..e9d256cc5ea8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -49,7 +49,8 @@ class OngoingCallController @Inject constructor( private val systemClock: SystemClock, private val activityStarter: ActivityStarter, @Main private val mainExecutor: Executor, - private val iActivityManager: IActivityManager + private val iActivityManager: IActivityManager, + private val logger: OngoingCallLogger ) : CallbackController<OngoingCallListener> { /** Null if there's no ongoing call. */ @@ -104,7 +105,7 @@ class OngoingCallController @Inject constructor( /** * Sets the chip view that will contain ongoing call information. * - * Should only be called from [CollapedStatusBarFragment]. + * Should only be called from [CollapsedStatusBarFragment]. */ fun setChipView(chipView: ViewGroup) { this.chipView = chipView @@ -113,6 +114,16 @@ class OngoingCallController @Inject constructor( } } + + /** + * Called when the chip's visibility may have changed. + * + * Should only be called from [CollapsedStatusBarFragment]. + */ + fun notifyChipVisibilityChanged(chipIsVisible: Boolean) { + logger.logChipVisibilityChanged(chipIsVisible) + } + /** * Returns true if there's an active ongoing call that should be displayed in a status bar chip. */ @@ -150,6 +161,7 @@ class OngoingCallController @Inject constructor( timeView.start() currentChipView.setOnClickListener { + logger.logChipClicked() activityStarter.postStartActivityDismissingKeyguard( currentOngoingCallInfo.intent, 0, ActivityLaunchAnimator.Controller.fromView(it)) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt new file mode 100644 index 000000000000..177f21537ec9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt @@ -0,0 +1,58 @@ +/* + * 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.statusbar.phone.ongoingcall + +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** A class to log events for the ongoing call chip. */ +@SysUISingleton +class OngoingCallLogger @Inject constructor(private val logger: UiEventLogger) { + + private var chipIsVisible: Boolean = false + + /** Logs that the ongoing call chip was clicked. */ + fun logChipClicked() { + logger.log(OngoingCallEvents.ONGOING_CALL_CLICKED) + } + + /** + * If needed, logs that the ongoing call chip's visibility has changed. + * + * For now, only logs when the chip changes from not visible to visible. + */ + fun logChipVisibilityChanged(chipIsVisible: Boolean) { + if (chipIsVisible && chipIsVisible != this.chipIsVisible) { + logger.log(OngoingCallEvents.ONGOING_CALL_VISIBLE) + } + this.chipIsVisible = chipIsVisible + } + + @VisibleForTesting + enum class OngoingCallEvents(val metricId: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "The ongoing call chip became visible") + ONGOING_CALL_VISIBLE(813), + + @UiEvent(doc = "The ongoing call chip was clicked") + ONGOING_CALL_CLICKED(814); + + override fun getId() = metricId + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 82ad00ad7c6d..2e75395cb5c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -30,6 +30,8 @@ import android.util.Log; import android.view.accessibility.AccessibilityManager; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; @@ -60,9 +62,28 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { private final ArrayMap<String, Long> mSnoozedPackages; private final AccessibilityManagerWrapper mAccessibilityMgr; + private final UiEventLogger mUiEventLogger; + + /** + * Enum entry for notification peek logged from this class. + */ + enum NotificationPeekEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Heads-up notification peeked on screen.") + NOTIFICATION_PEEK(801); + + private final int mId; + NotificationPeekEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + } + public HeadsUpManager(@NonNull final Context context) { mContext = context; mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); + mUiEventLogger = Dependency.get(UiEventLogger.class); Resources resources = context.getResources(); mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); mAutoDismissNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); @@ -130,6 +151,11 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { if (entry.isRowPinned() != isPinned) { entry.setRowPinned(isPinned); updatePinnedMode(); + if (isPinned && entry.getSbn() != null) { + mUiEventLogger.logWithInstanceId( + NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(), + entry.getSbn().getPackageName(), entry.getSbn().getInstanceId()); + } for (OnHeadsUpChangedListener listener : mListeners) { if (isPinned) { listener.onHeadsUpPinned(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 6a26e020a563..a6bec8c3713d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -371,11 +371,11 @@ public class NetworkControllerImpl extends BroadcastReceiver if (network.equals(mLastNetwork) && validated == lastValidated) { // Should not rely on getTransportTypes() returning the same order of transport // types. So sort the array before comparing. - int[] newTypes = networkCapabilities.getTransportTypes(); + int[] newTypes = getProcessedTransportTypes(networkCapabilities); Arrays.sort(newTypes); int[] lastTypes = (mLastNetworkCapabilities != null) - ? mLastNetworkCapabilities.getTransportTypes() : null; + ? getProcessedTransportTypes(mLastNetworkCapabilities) : null; if (lastTypes != null) Arrays.sort(lastTypes); if (Arrays.equals(newTypes, lastTypes)) { @@ -543,6 +543,21 @@ public class NetworkControllerImpl extends BroadcastReceiver return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; } + private int[] getProcessedTransportTypes(NetworkCapabilities networkCapabilities) { + int[] transportTypes = networkCapabilities.getTransportTypes(); + for (int i = 0; i < transportTypes.length; i++) { + // For VCN over WiFi, the transportType is set to be TRANSPORT_CELLULAR in the + // NetworkCapabilities, but we need to convert it into TRANSPORT_WIFI in order to + // distinguish it from VCN over Cellular. + if (transportTypes[i] == NetworkCapabilities.TRANSPORT_CELLULAR + && Utils.tryGetWifiInfoForVcn(networkCapabilities) != null) { + transportTypes[i] = NetworkCapabilities.TRANSPORT_WIFI; + break; + } + } + return transportTypes; + } + private MobileSignalController getDataController() { int dataSubId = mSubDefaults.getActiveDataSubId(); return getControllerWithSubId(dataSubId); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index edec61832c8e..41b1dd12639a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -23,6 +23,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ContrastColorUtil; @@ -58,15 +59,6 @@ public class SmartReplyView extends ViewGroup { /** Spacing to be applied between views. */ private final int mSpacing; - /** Horizontal padding of smart reply buttons if all of them use only one line of text. */ - private final int mSingleLineButtonPaddingHorizontal; - - /** Horizontal padding of smart reply buttons if at least one of them uses two lines of text. */ - private final int mDoubleLineButtonPaddingHorizontal; - - /** Increase in width of a smart reply button as a result of using two lines instead of one. */ - private final int mSingleToDoubleLineButtonWidthIncrease; - private final BreakIterator mBreakIterator; private PriorityQueue<Button> mCandidateButtonQueueForSqueezing; @@ -114,8 +106,6 @@ public class SmartReplyView extends ViewGroup { mDefaultBackgroundColor); int spacing = 0; - int singleLineButtonPaddingHorizontal = 0; - int doubleLineButtonPaddingHorizontal = 0; int strokeWidth = 0; final TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.SmartReplyView, @@ -125,10 +115,6 @@ public class SmartReplyView extends ViewGroup { int attr = arr.getIndex(i); if (attr == R.styleable.SmartReplyView_spacing) { spacing = arr.getDimensionPixelSize(i, 0); - } else if (attr == R.styleable.SmartReplyView_singleLineButtonPaddingHorizontal) { - singleLineButtonPaddingHorizontal = arr.getDimensionPixelSize(i, 0); - } else if (attr == R.styleable.SmartReplyView_doubleLineButtonPaddingHorizontal) { - doubleLineButtonPaddingHorizontal = arr.getDimensionPixelSize(i, 0); } else if (attr == R.styleable.SmartReplyView_buttonStrokeWidth) { strokeWidth = arr.getDimensionPixelSize(i, 0); } @@ -137,10 +123,6 @@ public class SmartReplyView extends ViewGroup { mStrokeWidth = strokeWidth; mSpacing = spacing; - mSingleLineButtonPaddingHorizontal = singleLineButtonPaddingHorizontal; - mDoubleLineButtonPaddingHorizontal = doubleLineButtonPaddingHorizontal; - mSingleToDoubleLineButtonWidthIncrease = - 2 * (doubleLineButtonPaddingHorizontal - singleLineButtonPaddingHorizontal); mBreakIterator = BreakIterator.getLineInstance(); @@ -222,6 +204,12 @@ public class SmartReplyView extends ViewGroup { return new LayoutParams(params.width, params.height); } + private void clearLayoutLineCount(View view) { + if (view instanceof TextView) { + ((TextView) view).nullLayouts(); + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int targetWidth = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED @@ -237,8 +225,7 @@ public class SmartReplyView extends ViewGroup { SmartSuggestionMeasures accumulatedMeasures = new SmartSuggestionMeasures( mPaddingLeft + mPaddingRight, - 0 /* maxChildHeight */, - mSingleLineButtonPaddingHorizontal); + 0 /* maxChildHeight */); int displayedChildCount = 0; // Set up a list of suggestions where actions come before replies. Note that the Buttons @@ -268,8 +255,7 @@ public class SmartReplyView extends ViewGroup { continue; } - child.setPadding(accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingTop(), - accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingBottom()); + clearLayoutLineCount(child); child.measure(MEASURE_SPEC_ANY_LENGTH, heightMeasureSpec); coveredSuggestions.add(child); @@ -299,18 +285,6 @@ public class SmartReplyView extends ViewGroup { accumulatedMeasures.mMaxChildHeight = Math.max(accumulatedMeasures.mMaxChildHeight, childHeight); - // Do we need to increase the number of lines in smart reply buttons to two? - final boolean increaseToTwoLines = - (accumulatedMeasures.mButtonPaddingHorizontal - == mSingleLineButtonPaddingHorizontal) - && (lineCount == 2 || accumulatedMeasures.mMeasuredWidth > targetWidth); - if (increaseToTwoLines) { - accumulatedMeasures.mMeasuredWidth += - (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease; - accumulatedMeasures.mButtonPaddingHorizontal = - mDoubleLineButtonPaddingHorizontal; - } - // If the last button doesn't fit into the remaining width, try squeezing preceding // smart reply buttons. if (accumulatedMeasures.mMeasuredWidth > targetWidth) { @@ -372,18 +346,11 @@ public class SmartReplyView extends ViewGroup { mCandidateButtonQueueForSqueezing.clear(); // Finally, we need to re-measure some buttons. - remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal, - accumulatedMeasures.mMaxChildHeight); + remeasureButtonsIfNecessary(accumulatedMeasures.mMaxChildHeight); int buttonHeight = Math.max(getSuggestedMinimumHeight(), mPaddingTop + accumulatedMeasures.mMaxChildHeight + mPaddingBottom); - // Set the corner radius to half the button height to make the side of the buttons look like - // a semicircle. - for (View smartSuggestionButton : smartSuggestions) { - setCornerRadius((Button) smartSuggestionButton, ((float) buttonHeight) / 2); - } - setMeasuredDimension( resolveSize(Math.max(getSuggestedMinimumWidth(), accumulatedMeasures.mMeasuredWidth), @@ -411,18 +378,14 @@ public class SmartReplyView extends ViewGroup { private static class SmartSuggestionMeasures { int mMeasuredWidth = -1; int mMaxChildHeight = -1; - int mButtonPaddingHorizontal = -1; - SmartSuggestionMeasures(int measuredWidth, int maxChildHeight, - int buttonPaddingHorizontal) { + SmartSuggestionMeasures(int measuredWidth, int maxChildHeight) { this.mMeasuredWidth = measuredWidth; this.mMaxChildHeight = maxChildHeight; - this.mButtonPaddingHorizontal = buttonPaddingHorizontal; } public SmartSuggestionMeasures clone() { - return new SmartSuggestionMeasures( - mMeasuredWidth, mMaxChildHeight, mButtonPaddingHorizontal); + return new SmartSuggestionMeasures(mMeasuredWidth, mMaxChildHeight); } } @@ -553,17 +516,11 @@ public class SmartReplyView extends ViewGroup { private int squeezeButtonToTextWidth(Button button, int heightMeasureSpec, int textWidth) { int oldWidth = button.getMeasuredWidth(); - if (button.getPaddingLeft() != mDoubleLineButtonPaddingHorizontal) { - // Correct for the fact that the button was laid out with single-line horizontal - // padding. - oldWidth += mSingleToDoubleLineButtonWidthIncrease; - } // Re-measure the squeezed smart reply button. - button.setPadding(mDoubleLineButtonPaddingHorizontal, button.getPaddingTop(), - mDoubleLineButtonPaddingHorizontal, button.getPaddingBottom()); + clearLayoutLineCount(button); final int widthMeasureSpec = MeasureSpec.makeMeasureSpec( - 2 * mDoubleLineButtonPaddingHorizontal + textWidth + button.getPaddingLeft() + button.getPaddingRight() + textWidth + getLeftCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST); button.measure(widthMeasureSpec, heightMeasureSpec); @@ -579,8 +536,7 @@ public class SmartReplyView extends ViewGroup { } } - private void remeasureButtonsIfNecessary( - int buttonPaddingHorizontal, int maxChildHeight) { + private void remeasureButtonsIfNecessary(int maxChildHeight) { final int maxChildHeightMeasure = MeasureSpec.makeMeasureSpec(maxChildHeight, MeasureSpec.EXACTLY); @@ -602,24 +558,7 @@ public class SmartReplyView extends ViewGroup { newWidth = Integer.MAX_VALUE; } - // Re-measure reason 2: The button's horizontal padding is incorrect (because it was - // measured with the wrong number of lines). - if (child.getPaddingLeft() != buttonPaddingHorizontal) { - requiresNewMeasure = true; - if (newWidth != Integer.MAX_VALUE) { - if (buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal) { - // Change padding (2->1 line). - newWidth -= mSingleToDoubleLineButtonWidthIncrease; - } else { - // Change padding (1->2 lines). - newWidth += mSingleToDoubleLineButtonWidthIncrease; - } - } - child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(), - buttonPaddingHorizontal, child.getPaddingBottom()); - } - - // Re-measure reason 3: The button's height is less than the max height of all buttons + // Re-measure reason 2: The button's height is less than the max height of all buttons // (all should have the same height). if (child.getMeasuredHeight() != maxChildHeight) { requiresNewMeasure = true; @@ -725,23 +664,6 @@ public class SmartReplyView extends ViewGroup { button.setTextColor(mCurrentTextColor); } - private void setCornerRadius(Button button, float radius) { - Drawable drawable = button.getBackground(); - if (drawable instanceof RippleDrawable) { - // Mutate in case other notifications are using this drawable. - drawable = drawable.mutate(); - RippleDrawable ripple = (RippleDrawable) drawable; - Drawable inset = ripple.getDrawable(0); - if (inset instanceof InsetDrawable) { - Drawable background = ((InsetDrawable) inset).getDrawable(); - if (background instanceof GradientDrawable) { - GradientDrawable gradientDrawable = (GradientDrawable) background; - gradientDrawable.setCornerRadius(radius); - } - } - } - } - enum SmartButtonType { REPLY, ACTION diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 3c1e12327d8f..865aa23f69e6 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -66,6 +66,13 @@ public class ThemeOverlayApplier implements Dumpable { "android.theme.customization.accent_color"; static final String OVERLAY_CATEGORY_SYSTEM_PALETTE = "android.theme.customization.system_palette"; + + static final String OVERLAY_COLOR_SOURCE = "android.theme.customization.color_source"; + + static final String COLOR_SOURCE_PRESET = "preset"; + + static final String TIMESTAMP_FIELD = "_applied_timestamp"; + @VisibleForTesting static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font"; @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 9bdd8c009531..195114f0f19f 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -15,8 +15,11 @@ */ package com.android.systemui.theme; +import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE; +import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD; import android.annotation.Nullable; import android.app.WallpaperColors; @@ -90,12 +93,12 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private final UserManager mUserManager; private final BroadcastDispatcher mBroadcastDispatcher; private final Executor mBgExecutor; - private final SecureSettings mSecureSettings; + private SecureSettings mSecureSettings; private final Executor mMainExecutor; private final Handler mBgHandler; private final WallpaperManager mWallpaperManager; private final boolean mIsMonetEnabled; - private final UserTracker mUserTracker; + private UserTracker mUserTracker; private DeviceProvisionedController mDeviceProvisionedController; private WallpaperColors mSystemColors; // If fabricated overlays were already created for the current theme. @@ -112,6 +115,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private boolean mAcceptColorEvents = true; // Defers changing themes until Setup Wizard is done. private boolean mDeferredThemeEvaluation; + // Determines if we should ignore THEME_CUSTOMIZATION_OVERLAY_PACKAGES setting changes. + private boolean mSkipSettingChange; private final DeviceProvisionedListener mDeviceProvisionedListener = new DeviceProvisionedListener() { @@ -162,6 +167,35 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } } } + // Check if we need to reset to default colors (if a color override was set that is sourced + // from the wallpaper) + int currentUser = mUserTracker.getUserId(); + String overlayPackageJson = mSecureSettings.getStringForUser( + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + currentUser); + if (!TextUtils.isEmpty(overlayPackageJson)) { + try { + JSONObject jsonObject = new JSONObject(overlayPackageJson); + if ((jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) + || jsonObject.has(OVERLAY_CATEGORY_SYSTEM_PALETTE)) + && !COLOR_SOURCE_PRESET.equals( + jsonObject.optString(OVERLAY_COLOR_SOURCE))) { + mSkipSettingChange = true; + jsonObject.remove(OVERLAY_CATEGORY_ACCENT_COLOR); + jsonObject.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + jsonObject.remove(OVERLAY_COLOR_SOURCE); + jsonObject.put(TIMESTAMP_FIELD, System.currentTimeMillis()); + if (DEBUG) { + Log.d(TAG, "Updating theme setting from " + + overlayPackageJson + " to " + jsonObject.toString()); + } + mSecureSettings.putString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + jsonObject.toString()); + } + } catch (JSONException e) { + Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); + } + } reevaluateSystemTheme(false /* forceReload */); }; @@ -232,6 +266,11 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mDeferredThemeEvaluation = true; return; } + if (mSkipSettingChange) { + if (DEBUG) Log.d(TAG, "Skipping setting change"); + mSkipSettingChange = false; + return; + } reevaluateSystemTheme(true /* forceReload */); } }, diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt new file mode 100644 index 000000000000..647faeba1e77 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt @@ -0,0 +1,58 @@ +/* + * 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.util.concurrency + +import android.os.Looper +import javax.inject.Inject + +/** + * Methods to check or assert that we're on the main thread + */ +interface Execution { + fun assertIsMainThread() + fun isMainThread(): Boolean +} + +class ExecutionImpl @Inject constructor() : Execution { + private val mainLooper = Looper.getMainLooper() + + override fun assertIsMainThread() { + if (!mainLooper.isCurrentThread) { + throw IllegalStateException("should be called from the main thread." + + " Main thread name=" + mainLooper.thread.name + + " Thread.currentThread()=" + Thread.currentThread().name) + } + } + + override fun isMainThread(): Boolean { + return mainLooper.isCurrentThread + } +} + +class FakeExecution : Execution { + var simulateMainThread = true + + override fun assertIsMainThread() { + if (!simulateMainThread) { + throw IllegalStateException("should be called from the main thread") + } + } + + override fun isMainThread(): Boolean { + return simulateMainThread + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java index 5946af383b0f..1c504961e715 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java @@ -24,6 +24,8 @@ import com.android.systemui.dagger.qualifiers.Main; import java.util.concurrent.Executor; +import javax.inject.Singleton; + import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -40,7 +42,7 @@ public abstract class GlobalConcurrencyModule { @Binds public abstract ThreadFactory bindExecutorFactory(ThreadFactoryImpl impl); - /** Main Looper */ + /** Main Looper */ @Provides @Main public static Looper provideMainLooper() { @@ -67,4 +69,8 @@ public abstract class GlobalConcurrencyModule { return context.getMainExecutor(); } + /** */ + @Binds + @Singleton + public abstract Execution provideExecution(ExecutionImpl execution); } diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java index 2270f9631094..7a5ceb547d9e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java @@ -16,6 +16,7 @@ package com.android.systemui.util.concurrency; +import android.os.Handler; import android.os.Looper; import java.util.concurrent.Executor; @@ -29,6 +30,14 @@ import java.util.concurrent.Executor; */ public interface ThreadFactory { /** + * Returns a {@link Handler} running on a named thread. + * + * The thread is implicitly started and may be left running indefinitely, depending on the + * implementation. Assume this is the case and use responsibly. + */ + Handler builderHandlerOnNewThread(String threadName); + + /** * Return an {@link java.util.concurrent.Executor} running on a named thread. * * The thread is implicitly started and may be left running indefinitely, depending on the @@ -45,6 +54,11 @@ public interface ThreadFactory { DelayableExecutor buildDelayableExecutorOnNewThread(String threadName); /** + * Return an {@link DelayableExecutor} running on the given HandlerThread. + **/ + DelayableExecutor buildDelayableExecutorOnHandler(Handler handler); + + /** * Return an {@link DelayableExecutor} running the given Looper **/ DelayableExecutor buildDelayableExecutorOnLooper(Looper looper); diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java index 2d9f2b424aae..184b83113d8d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java @@ -16,6 +16,7 @@ package com.android.systemui.util.concurrency; +import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -27,16 +28,31 @@ class ThreadFactoryImpl implements ThreadFactory { @Inject ThreadFactoryImpl() {} + @Override + public Handler builderHandlerOnNewThread(String threadName) { + HandlerThread handlerThread = new HandlerThread(threadName); + handlerThread.start(); + return new Handler(handlerThread.getLooper()); + } + + @Override public Executor buildExecutorOnNewThread(String threadName) { return buildDelayableExecutorOnNewThread(threadName); } + @Override public DelayableExecutor buildDelayableExecutorOnNewThread(String threadName) { HandlerThread handlerThread = new HandlerThread(threadName); handlerThread.start(); - return new ExecutorImpl(handlerThread.getLooper()); + return buildDelayableExecutorOnLooper(handlerThread.getLooper()); + } + + @Override + public DelayableExecutor buildDelayableExecutorOnHandler(Handler handler) { + return buildDelayableExecutorOnLooper(handler.getLooper()); } + @Override public DelayableExecutor buildDelayableExecutorOnLooper(Looper looper) { return new ExecutorImpl(looper); } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java index 644addfef1d1..5e9bae98a17e 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java @@ -20,13 +20,10 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.text.TextPaint; import android.util.MathUtils; import android.view.View; -import android.widget.TextView; import androidx.annotation.ColorInt; -import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.RecyclerView; @@ -39,12 +36,10 @@ final class DotIndicatorDecoration extends RecyclerView.ItemDecoration { @ColorInt private final int mUnselectedColor; @ColorInt private final int mSelectedColor; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); private WalletCardCarousel mCardCarousel; DotIndicatorDecoration(Context context) { super(); - mUnselectedRadius = context.getResources().getDimensionPixelSize( R.dimen.card_carousel_dot_unselected_radius); @@ -53,13 +48,8 @@ final class DotIndicatorDecoration extends RecyclerView.ItemDecoration { R.dimen.card_carousel_dot_selected_radius); mDotMargin = context.getResources().getDimensionPixelSize(R.dimen.card_carousel_dot_margin); - TextView textView = new TextView(context); - mTextPaint.set(textView.getPaint()); - // Text color is not copied from text appearance. - mTextPaint.setColor(ContextCompat.getColor(context, R.color.GM2_blue_600)); - - mUnselectedColor = ContextCompat.getColor(context, R.color.GM2_grey_300); - mSelectedColor = ContextCompat.getColor(context, R.color.GM2_blue_600); + mUnselectedColor = context.getColor(com.android.internal.R.color.system_neutral1_300); + mSelectedColor = context.getColor(com.android.internal.R.color.system_neutral1_0); } @Override @@ -107,9 +97,9 @@ final class DotIndicatorDecoration extends RecyclerView.ItemDecoration { int i = isLayoutLtr() ? itemsDrawn : itemCount - itemsDrawn - 1; if (isSelectedItem(i)) { - drawSelectedDot(canvas, interpolatedProgress, i); + drawSelectedDot(canvas, interpolatedProgress); } else if (isNextItemInScrollingDirection(i)) { - drawFadingUnselectedDot(canvas, interpolatedProgress, i); + drawFadingUnselectedDot(canvas, interpolatedProgress); } else { drawUnselectedDot(canvas); } @@ -121,7 +111,7 @@ final class DotIndicatorDecoration extends RecyclerView.ItemDecoration { this.mCardCarousel = null; // No need to hold a reference. } - private void drawSelectedDot(Canvas canvas, float progress, int position) { + private void drawSelectedDot(Canvas canvas, float progress) { // Divide progress by 2 because the other half of the animation is done by // drawFadingUnselectedDot. mPaint.setColor( @@ -132,13 +122,13 @@ final class DotIndicatorDecoration extends RecyclerView.ItemDecoration { canvas.translate(radius * 2, 0); } - private void drawFadingUnselectedDot(Canvas canvas, float progress, int position) { + private void drawFadingUnselectedDot(Canvas canvas, float progress) { // Divide progress by 2 because the first half of the animation is done by drawSelectedDot. int blendedColor = ColorUtils.blendARGB( mUnselectedColor, mSelectedColor, progress / 2); mPaint.setColor(getTransitionAdjustedColor(blendedColor)); - float radius = MathUtils.lerp(mSelectedRadius, mUnselectedRadius, progress / 2); + float radius = MathUtils.lerp(mUnselectedRadius, mSelectedColor, progress / 2); canvas.drawCircle(radius, 0, radius, mPaint); canvas.translate(radius * 2, 0); } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java index ac8b16a2206d..66bd48b9d188 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java @@ -17,6 +17,7 @@ package com.android.systemui.wallet.ui; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.service.quickaccesswallet.QuickAccessWalletClient; @@ -95,7 +96,7 @@ public class WalletActivity extends LifecycleActivity { } setTitle(""); getActionBar().setDisplayHomeAsUpEnabled(true); - getActionBar().setHomeAsUpIndicator(R.drawable.ic_close); + getActionBar().setHomeAsUpIndicator(getHomeIndicatorDrawable()); getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close); WalletView walletView = requireViewById(R.id.wallet_view); mWalletScreenController = new WalletScreenController( @@ -175,4 +176,10 @@ public class WalletActivity extends LifecycleActivity { mWalletScreenController.onDismissed(); super.onDestroy(); } + + private Drawable getHomeIndicatorDrawable() { + Drawable drawable = getDrawable(R.drawable.ic_close); + drawable.setTint(getColor(com.android.internal.R.color.system_neutral1_300)); + return drawable; + } } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index ec62981cae73..b57d93762381 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -234,7 +234,9 @@ public class WalletScreenController implements mWalletView.show(); mWalletView.hideErrorMessage(); int iconSizePx = - mContext.getResources().getDimensionPixelSize(R.dimen.wallet_view_header_icon_size); + mContext + .getResources() + .getDimensionPixelSize(R.dimen.wallet_screen_header_icon_size); GetWalletCardsRequest request = new GetWalletCardsRequest(cardWidthPx, cardHeightPx, iconSizePx, MAX_CARDS); mWalletClient.getWalletCards(mExecutor, request, this); @@ -340,7 +342,11 @@ public class WalletScreenController implements @Override public CharSequence getLabel() { - return mWalletCard.getCardLabel(); + CharSequence label = mWalletCard.getCardLabel(); + if (label == null) { + return ""; + } + return label; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java index c547bb346617..e42ce6ac50f3 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java @@ -37,6 +37,7 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.Utils; import com.android.systemui.R; import java.util.List; @@ -100,7 +101,7 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard public void onCardScroll(WalletCardViewInfo centerCard, WalletCardViewInfo nextCard, float percentDistanceFromCenter) { CharSequence centerCardText = getLabelText(centerCard); - Drawable centerCardIcon = centerCard.getIcon(); + Drawable centerCardIcon = getHeaderIcon(mContext, centerCard); if (!TextUtils.equals(mCenterCardText, centerCardText)) { mCenterCardText = centerCardText; mCardLabel.setText(centerCardText); @@ -133,7 +134,8 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard mCardCarouselContainer.setVisibility(VISIBLE); mErrorView.setVisibility(GONE); mEmptyStateView.setVisibility(GONE); - renderHeaderIconAndActionButton(data.get(selectedIndex), isDeviceLocked); + mIcon.setImageDrawable(getHeaderIcon(mContext, data.get(selectedIndex))); + renderActionButton(data.get(selectedIndex), isDeviceLocked); if (shouldAnimate) { animateViewsShown(mIcon, mCardLabel, mActionButton); } @@ -220,10 +222,15 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard return mCardLabel; } - private void renderHeaderIconAndActionButton(WalletCardViewInfo walletCard, boolean isLocked) { - mIcon.setImageDrawable(walletCard.getIcon()); - mIcon.setVisibility(VISIBLE); - renderActionButton(walletCard, isLocked); + @Nullable + private static Drawable getHeaderIcon(Context context, WalletCardViewInfo walletCard) { + Drawable icon = walletCard.getIcon(); + if (icon != null) { + icon.setTint( + Utils.getColorAttrDefaultColor( + context, com.android.internal.R.attr.colorAccentPrimary)); + } + return icon; } private void renderActionButton(WalletCardViewInfo walletCard, boolean isDeviceLocked) { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 26b68afed494..6c306743e4ce 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -280,8 +280,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { - return new PipSurfaceTransactionHelper(context); + static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper() { + return new PipSurfaceTransactionHelper(); } @WMSingleton |