diff options
Diffstat (limited to 'packages/SystemUI/src')
199 files changed, 5884 insertions, 3730 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 0a117c17a354..1569fff63453 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -21,9 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Color; -import android.graphics.Paint; import android.icu.text.NumberFormat; -import android.util.MathUtils; import com.android.settingslib.Utils; import com.android.systemui.R; @@ -94,11 +92,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie mStatusBarStateController.removeCallback(mStatusBarStateListener); } - float getClockTextTopPadding() { - Paint.FontMetrics fm = mView.getPaint().getFontMetrics(); - return MathUtils.abs(fm.ascent - fm.top); - } - /** * Updates the time for the view. */ diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java index f01b67b7b5c2..945c9c499401 100644 --- a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java +++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java @@ -20,6 +20,7 @@ import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import android.hardware.biometrics.BiometricSourceType; import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; @@ -73,13 +74,15 @@ public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> i @Override protected void onViewAttached() { - mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); - - mStatusBarStateController.addCallback(mStatusBarStateListener); mIsKeyguardShowing = mStatusBarStateController.getState() == StatusBarState.KEYGUARD; mIsDozing = mStatusBarStateController.isDozing(); + mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); mAuthenticated = false; + updateButtonVisibility(); + + mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); + mStatusBarStateController.addCallback(mStatusBarStateListener); } @Override @@ -88,6 +91,15 @@ public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> i mStatusBarStateController.removeCallback(mStatusBarStateListener); } + /** + * Call when this controller is no longer needed. This will remove the view from its parent. + */ + public void destroy() { + if (mView != null && mView.getParent() != null) { + ((ViewGroup) mView.getParent()).removeView(mView); + } + } + private void updateButtonVisibility() { mShowButton = !mAuthenticated && !mIsDozing && mIsKeyguardShowing && !mIsBouncerShowing && !mRunningFPS; @@ -143,6 +155,14 @@ public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> i public void onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType) { mRunningFPS = running && biometricSourceType == FINGERPRINT; + mAuthenticated &= !mRunningFPS; + updateButtonVisibility(); + } + + @Override + public void onBiometricAuthenticated(int userId, + BiometricSourceType biometricSourceType, boolean isStrongBiometric) { + mAuthenticated = true; updateButtonVisibility(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 6eb54c25a440..93ed0eaa3fba 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -179,20 +179,15 @@ public class KeyguardClockSwitch extends RelativeLayout { setPaddingRelative(startEndPadding, 0, startEndPadding, 0); mSmallClockFrame.setVisibility(GONE); mNewLockscreenClockFrame.setVisibility(VISIBLE); - - statusAreaLP.removeRule(RelativeLayout.BELOW); + statusAreaLP.addRule(RelativeLayout.BELOW, R.id.new_lockscreen_clock_view); statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START); - statusAreaLP.addRule(RelativeLayout.START_OF, R.id.new_lockscreen_clock_view); - statusAreaLP.width = 0; } else { setPaddingRelative(0, 0, 0, 0); mSmallClockFrame.setVisibility(VISIBLE); mNewLockscreenClockFrame.setVisibility(GONE); statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START); - statusAreaLP.removeRule(RelativeLayout.START_OF); statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view); - statusAreaLP.width = ViewGroup.LayoutParams.MATCH_PARENT; } requestLayout(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index e375877ed6cf..0675200f81e2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -20,6 +20,7 @@ import android.app.WallpaperManager; import android.content.ContentResolver; import android.content.res.Resources; import android.provider.Settings; +import android.text.TextUtils; import android.text.format.DateFormat; import android.view.View; import android.view.ViewGroup; @@ -212,10 +213,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS * keep the clock centered. */ void updatePosition(int x, float scale, AnimationProperties props, boolean animate) { - x = Math.abs(x); + x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x; if (mNewLockScreenClockFrame != null) { PropertyAnimator.setProperty(mNewLockScreenClockFrame, AnimatableProperty.TRANSLATION_X, - -x, props, animate); + x, props, animate); PropertyAnimator.setProperty(mNewLockScreenLargeClockFrame, AnimatableProperty.SCALE_X, scale, props, animate); PropertyAnimator.setProperty(mNewLockScreenLargeClockFrame, AnimatableProperty.SCALE_Y, @@ -277,15 +278,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS refreshFormat(mTimeFormat); } - float getClockTextTopPadding() { - if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1 - && mNewLockScreenClockViewController != null) { - return mNewLockScreenClockViewController.getClockTextTopPadding(); - } - - return mView.getClockTextTopPadding(); - } - private void updateAodIcons() { NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( @@ -337,4 +329,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS sCacheKey = key; } } + + private int getCurrentLayoutDirection() { + return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 92b65b242b0e..533bec14f60a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -136,14 +136,23 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { @Override public void startAppearAnimation() { - // Reset state, and let IME animation reveal the view as it slides in + // Reset state, and let IME animation reveal the view as it slides in, if one exists. + // It is possible for an IME to have no view, so provide a default animation since no + // calls to animateForIme would occur setAlpha(0f); + animate() + .alpha(1f) + .setDuration(500) + .setStartDelay(300) + .start(); + setTranslationY(0f); } @Override public void animateForIme(float interpolatedFraction) { - setAlpha(interpolatedFraction); + animate().cancel(); + setAlpha(Math.max(interpolatedFraction, getAlpha())); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 5f6fd30ffa1b..a2d7707a1569 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -29,12 +29,17 @@ import android.app.AlertDialog; import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; +import android.provider.Settings; import android.util.AttributeSet; import android.util.MathUtils; import android.util.TypedValue; +import android.view.Gravity; import android.view.MotionEvent; +import android.view.OrientationEventListener; import android.view.VelocityTracker; +import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewPropertyAnimator; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimationControlListener; @@ -55,6 +60,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.List; @@ -99,6 +105,12 @@ public class KeyguardSecurityContainer extends FrameLayout { private boolean mDisappearAnimRunning; private SwipeListener mSwipeListener; + private boolean mIsSecurityViewLeftAligned = true; + private boolean mOneHandedMode = false; + private SecurityMode mSecurityMode = SecurityMode.Invalid; + private ViewPropertyAnimator mRunningOneHandedAnimator; + private final OrientationEventListener mOrientationEventListener; + private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -157,16 +169,20 @@ public class KeyguardSecurityContainer extends FrameLayout { // Used to notify the container when something interesting happens. public interface SecurityCallback { boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen); + void userActivity(); + void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); /** - * @param strongAuth wheher the user has authenticated with strong authentication like - * pattern, password or PIN but not by trust agents or fingerprint + * @param strongAuth wheher the user has authenticated with strong authentication like + * pattern, password or PIN but not by trust agents or fingerprint * @param targetUserId a user that needs to be the foreground user at the finish completion. */ void finish(boolean strongAuth, int targetUserId); + void reset(); + void onCancelClicked(); } @@ -224,12 +240,136 @@ public class KeyguardSecurityContainer extends FrameLayout { super(context, attrs, defStyle); mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); mViewConfiguration = ViewConfiguration.get(context); + + mOrientationEventListener = new OrientationEventListener(context) { + @Override + public void onOrientationChanged(int orientation) { + updateLayoutForSecurityMode(mSecurityMode); + } + }; } void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { + mSecurityMode = securityMode; mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); updateBiometricRetry(securityMode, faceAuthEnabled); + updateLayoutForSecurityMode(securityMode); + mOrientationEventListener.enable(); + } + + void updateLayoutForSecurityMode(SecurityMode securityMode) { + mSecurityMode = securityMode; + mOneHandedMode = canUseOneHandedBouncer(); + + if (mOneHandedMode) { + mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext); + } + + updateSecurityViewGravity(); + updateSecurityViewLocation(false); + } + + /** Return whether the one-handed keyguard should be enabled. */ + private boolean canUseOneHandedBouncer() { + // Is it enabled? + if (!getResources().getBoolean( + com.android.internal.R.bool.config_enableOneHandedKeyguard)) { + return false; + } + + if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) { + return false; + } + + return getResources().getBoolean(R.bool.can_use_one_handed_bouncer); + } + + /** Read whether the one-handed keyguard should be on the left/right from settings. */ + private boolean isOneHandedKeyguardLeftAligned(Context context) { + try { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ONE_HANDED_KEYGUARD_SIDE) + == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; + } catch (Settings.SettingNotFoundException ex) { + return true; + } + } + + private void updateSecurityViewGravity() { + View securityView = findKeyguardSecurityView(); + + if (securityView == null) { + return; + } + + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams(); + + if (mOneHandedMode) { + lp.gravity = Gravity.LEFT | Gravity.BOTTOM; + } else { + lp.gravity = Gravity.CENTER_HORIZONTAL; + } + + securityView.setLayoutParams(lp); + } + + /** + * Moves the inner security view to the correct location (in one handed mode) with animation. + * This is triggered when the user taps on the side of the screen that is not currently occupied + * by the security view . + */ + private void updateSecurityViewLocation(boolean animate) { + View securityView = findKeyguardSecurityView(); + + if (securityView == null) { + return; + } + + if (!mOneHandedMode) { + securityView.setTranslationX(0); + return; + } + + if (mRunningOneHandedAnimator != null) { + mRunningOneHandedAnimator.cancel(); + mRunningOneHandedAnimator = null; + } + + int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f); + + if (animate) { + mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation); + mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningOneHandedAnimator = null; + } + }); + + mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + mRunningOneHandedAnimator.start(); + } else { + securityView.setTranslationX(targetTranslation); + } + } + + @Nullable + private KeyguardSecurityViewFlipper findKeyguardSecurityView() { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + + if (isKeyguardSecurityView(child)) { + return (KeyguardSecurityViewFlipper) child; + } + } + + return null; + } + + private boolean isKeyguardSecurityView(View view) { + return view instanceof KeyguardSecurityViewFlipper; } public void onPause() { @@ -238,6 +378,7 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog = null; } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); + mOrientationEventListener.disable(); } @Override @@ -319,19 +460,44 @@ public class KeyguardSecurityContainer extends FrameLayout { if (mSwipeListener != null) { mSwipeListener.onSwipeUp(); } + } else { + if (!mIsDragging) { + handleTap(event); + } } } return true; } + private void handleTap(MotionEvent event) { + // If we're using a fullscreen security mode, skip + if (!mOneHandedMode) { + return; + } + + // Did the tap hit the "other" side of the bouncer? + if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f)) + || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) { + mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned; + + Settings.Global.putInt( + mContext.getContentResolver(), + Settings.Global.ONE_HANDED_KEYGUARD_SIDE, + mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT + : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); + + updateSecurityViewLocation(true); + } + } + void setSwipeListener(SwipeListener swipeListener) { mSwipeListener = swipeListener; } private void startSpringAnimation(float startVelocity) { mSpringAnimation - .setStartVelocity(startVelocity) - .animateToFinalPosition(0); + .setStartVelocity(startVelocity) + .animateToFinalPosition(0); } public void startDisappearAnimation(SecurityMode securitySelection) { @@ -441,18 +607,17 @@ public class KeyguardSecurityContainer extends FrameLayout { return insets.inset(0, 0, 0, inset); } - private void showDialog(String title, String message) { if (mAlertDialog != null) { mAlertDialog.dismiss(); } mAlertDialog = new AlertDialog.Builder(mContext) - .setTitle(title) - .setMessage(message) - .setCancelable(false) - .setNeutralButton(R.string.ok, null) - .create(); + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setNeutralButton(R.string.ok, null) + .create(); if (!(mContext instanceof Activity)) { mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); } @@ -490,6 +655,47 @@ public class KeyguardSecurityContainer extends FrameLayout { } } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthMeasureSpec) / 2, + MeasureSpec.getMode(widthMeasureSpec)); + + for (int i = 0; i < getChildCount(); i++) { + final View view = getChildAt(i); + if (view.getVisibility() != GONE) { + if (mOneHandedMode && isKeyguardSecurityView(view)) { + measureChildWithMargins(view, halfWidthMeasureSpec, 0, + heightMeasureSpec, 0); + } else { + measureChildWithMargins(view, widthMeasureSpec, 0, + heightMeasureSpec, 0); + } + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + maxWidth = Math.max(maxWidth, + view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, view.getMeasuredState()); + } + } + + maxWidth += getPaddingLeft() + getPaddingRight(); + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against our minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + } + void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { String message = null; switch (userType) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 1a8d420fb394..fdab8db67431 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -404,6 +404,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (newView != null) { newView.onResume(KeyguardSecurityView.VIEW_REVEALED); mSecurityViewFlipperController.show(newView); + mView.updateLayoutForSecurityMode(securityMode); } mSecurityCallback.onSecurityModeChanged( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java index 29d09914a9b4..2b73894e3fd8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java @@ -93,4 +93,13 @@ public class KeyguardSecurityModel { throw new IllegalStateException("Unknown security quality:" + security); } } + + /** + * Returns whether the given security view should be used in a "one handed" way. This can be + * used to change how the security view is drawn (e.g. take up less of the screen, and align to + * one side). + */ + public static boolean isSecurityViewOneHanded(SecurityMode securityMode) { + return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index fb97a30f93fb..83c2d1e7f684 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -85,10 +85,6 @@ public class KeyguardSliceView extends LinearLayout { */ private Runnable mContentChangeListener; private boolean mHasHeader; - private final int mRowWithHeaderPadding; - private final int mRowPadding; - private float mRowTextSize; - private float mRowWithHeaderTextSize; private View.OnClickListener mOnClickListener; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; @@ -97,9 +93,6 @@ public class KeyguardSliceView extends LinearLayout { super(context, attrs); Resources resources = context.getResources(); - mRowPadding = resources.getDimensionPixelSize(R.dimen.subtitle_clock_padding); - mRowWithHeaderPadding = resources.getDimensionPixelSize(R.dimen.header_subtitle_padding); - mLayoutTransition = new LayoutTransition(); mLayoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2); mLayoutTransition.setDuration(LayoutTransition.APPEARING, DEFAULT_ANIM_DURATION); @@ -120,10 +113,6 @@ public class KeyguardSliceView extends LinearLayout { mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size); - mRowTextSize = mContext.getResources().getDimensionPixelSize( - R.dimen.widget_label_font_size); - mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize( - R.dimen.header_row_font_size); mTitle.setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED); } @@ -204,7 +193,6 @@ public class KeyguardSliceView extends LinearLayout { LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams(); layoutParams.gravity = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL ? Gravity.START : Gravity.CENTER; - layoutParams.topMargin = mHasHeader ? mRowWithHeaderPadding : mRowPadding; mRow.setLayoutParams(layoutParams); for (int i = startIndex; i < subItemsCount; i++) { @@ -230,8 +218,6 @@ public class KeyguardSliceView extends LinearLayout { final SliceItem titleItem = rc.getTitleItem(); button.setText(titleItem == null ? null : titleItem.getText()); button.setContentDescription(rc.getContentDescription()); - button.setTextSize(TypedValue.COMPLEX_UNIT_PX, - mHasHeader ? mRowWithHeaderTextSize : mRowTextSize); Drawable iconDrawable = null; SliceItem icon = SliceQuery.find(item.getSlice(), @@ -313,10 +299,6 @@ public class KeyguardSliceView extends LinearLayout { void onDensityOrFontScaleChanged() { mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size); mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size); - mRowTextSize = mContext.getResources().getDimensionPixelSize( - R.dimen.widget_label_font_size); - mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize( - R.dimen.header_row_font_size); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -423,14 +405,19 @@ public class KeyguardSliceView extends LinearLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ public void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; - boolean wasAwake = mDarkAmount != 0; - if (isAwake == wasAwake) { + boolean isDozing = darkAmount != 0; + boolean wasDozing = mDarkAmount != 0; + if (isDozing == wasDozing) { return; } mDarkAmount = darkAmount; - setLayoutAnimationListener(isAwake ? null : mKeepAwakeListener); + setLayoutAnimationListener(isDozing ? null : mKeepAwakeListener); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 6fb6760be653..934e768f9ba8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -266,15 +266,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardClockSwitchController.updateLockScreenMode(mode); mKeyguardSliceViewController.updateLockScreenMode(mode); if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { - // align the top of keyguard_status_area with the top of the clock text instead - // of the top of the view - mKeyguardSliceViewController.updateTopMargin( - mKeyguardClockSwitchController.getClockTextTopPadding()); mView.setCanShowOwnerInfo(false); mView.setCanShowLogout(false); } else { - // reset margin - mKeyguardSliceViewController.updateTopMargin(0); mView.setCanShowOwnerInfo(true); mView.setCanShowLogout(false); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 1d550313359a..1858438f6a10 100755 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -306,6 +306,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLogoutEnabled; // cached value to avoid IPCs private boolean mIsUdfpsEnrolled; + private boolean mKeyguardQsUserSwitchEnabled; // If the user long pressed the lock icon, disabling face auth for the current session. private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -1917,7 +1918,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser()) && !isUdfpsEnrolled(); } - return true; + return !isKeyguardQsUserSwitchEnabled(); } /** @@ -1927,6 +1928,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mIsUdfpsEnrolled; } + /** + * @return true if the keyguard qs user switcher shortcut is enabled + */ + public boolean isKeyguardQsUserSwitchEnabled() { + return mKeyguardQsUserSwitchEnabled; + } + + public void setKeyguardQsUserSwitchEnabled(boolean enabled) { + mKeyguardQsUserSwitchEnabled = enabled; + } + private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() { @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) { @@ -2066,8 +2078,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean shouldListenForUdfps() { return shouldListenForFingerprint() && !mBouncer - && mStatusBarState != StatusBarState.SHADE_LOCKED - && mStatusBarState != StatusBarState.FULLSCREEN_USER_SWITCHER && mStrongAuthTracker.hasUserAuthenticatedSinceBoot(); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 8cb1bc4878a5..5db3349b646a 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -69,7 +69,9 @@ public class NumPadButton extends AlphaOptimizedImageButton { @Override public boolean onTouchEvent(MotionEvent event) { - if (mAnimator != null) mAnimator.start(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (mAnimator != null) mAnimator.start(); + } return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index a4a781dc6ff5..e6a9d4fdd547 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -160,10 +160,9 @@ public class NumPadKey extends ViewGroup { public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { doHapticKeyClick(); + if (mAnimator != null) mAnimator.start(); } - if (mAnimator != null) mAnimator.start(); - return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index fe0ae33d17f1..ae4c8e5a3327 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -59,6 +59,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; import com.android.systemui.privacy.PrivacyItemController; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.screenrecord.RecordingController; @@ -246,6 +247,7 @@ public class Dependency { @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor; @Inject Lazy<BatteryController> mBatteryController; @Inject Lazy<NightDisplayListener> mNightDisplayListener; + @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController; @Inject Lazy<ManagedProfileController> mManagedProfileController; @Inject Lazy<NextAlarmController> mNextAlarmController; @Inject Lazy<DataSaverController> mDataSaverController; @@ -393,6 +395,8 @@ public class Dependency { mProviders.put(NightDisplayListener.class, mNightDisplayListener::get); + mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get); + mProviders.put(ManagedProfileController.class, mManagedProfileController::get); mProviders.put(NextAlarmController.class, mNextAlarmController::get); diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 3852b24fe4b3..fba34e0b8ad3 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -367,7 +367,7 @@ public class SwipeHelper implements Gefingerpoken { } /** - * @param view The view to be dismissed + * @param animView The view to be dismissed * @param velocity The desired pixels/second speed at which the view should move * @param endAction The action to perform at the end * @param delay The delay after which we should start @@ -477,12 +477,8 @@ public class SwipeHelper implements Gefingerpoken { public void snapChild(final View animView, final float targetLeft, float velocity) { final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); - AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed); - } - }; + AnimatorUpdateListener updateListener = animation -> onTranslationUpdate(animView, + (float) animation.getAnimatedValue(), canBeDismissed); Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener); if (anim == null) { @@ -501,8 +497,6 @@ public class SwipeHelper implements Gefingerpoken { mSnappingChild = false; if (!wasCancelled) { updateSwipeProgressFromOffset(animView, canBeDismissed); - onChildSnappedBack(animView, targetLeft); - mCallback.onChildSnappedBack(animView, targetLeft); resetSwipeState(); } } @@ -513,6 +507,7 @@ public class SwipeHelper implements Gefingerpoken { mFlingAnimationUtils.apply(anim, getTranslation(animView), targetLeft, velocity, maxDistance); anim.start(); + mCallback.onChildSnappedBack(animView, targetLeft); } /** @@ -594,13 +589,12 @@ public class SwipeHelper implements Gefingerpoken { if (!mIsSwiping && !mMenuRowIntercepting) { if (mCallback.getChildAtPosition(ev) != null) { - // We are dragging directly over a card, make sure that we also catch the gesture // even if nobody else wants the touch event. + mTouchedView = mCallback.getChildAtPosition(ev); onInterceptTouchEvent(ev); return true; } else { - // We are not doing anything, make sure the long press callback // is not still ticking like a bomb waiting to go off. cancelLongPress(); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 865ca40b1f4c..59c0fb816a96 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -19,15 +19,18 @@ package com.android.systemui; import android.app.ActivityThread; import android.app.Application; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Process; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.util.TimingsTraceLog; import android.view.SurfaceControl; @@ -37,6 +40,8 @@ import com.android.systemui.dagger.ContextComponentHelper; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; +import com.android.systemui.people.PeopleSpaceActivity; +import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import com.android.systemui.shared.system.ThreadedRendererCompat; import com.android.systemui.util.NotificationChannels; @@ -121,6 +126,26 @@ public class SystemUIApplication extends Application implements mServices[i].onBootCompleted(); } } + // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider. + // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto) + try { + int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.SHOW_PEOPLE_SPACE, 1); + context.getPackageManager().setComponentEnabledSetting( + new ComponentName(context, PeopleSpaceWidgetProvider.class), + showPeopleSpace == 1 + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + context.getPackageManager().setComponentEnabledSetting( + new ComponentName(context, PeopleSpaceActivity.class), + showPeopleSpace == 1 + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } catch (Exception e) { + Log.w(TAG, "Error enabling People Space widget:", e); + } } }, bootCompletedFilter); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 19520df08757..9d00262436e5 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -115,7 +115,8 @@ public class SystemUIFactory { .setShellCommandHandler(mWMComponent.getShellCommandHandler()) .setAppPairs(mWMComponent.getAppPairs()) .setTaskViewFactory(mWMComponent.getTaskViewFactory()) - .setTransitions(mWMComponent.getTransitions()); + .setTransitions(mWMComponent.getTransitions()) + .setStartingSurface(mWMComponent.getStartingSurface()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -129,7 +130,8 @@ public class SystemUIFactory { .setShellCommandHandler(Optional.ofNullable(null)) .setAppPairs(Optional.ofNullable(null)) .setTaskViewFactory(Optional.ofNullable(null)) - .setTransitions(Transitions.createEmptyForTesting()); + .setTransitions(Transitions.createEmptyForTesting()) + .setStartingSurface(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (initializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index d8ca63960b30..2040347de1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -16,8 +16,8 @@ package com.android.systemui.appops; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; import android.Manifest; @@ -40,6 +40,7 @@ import android.util.SparseArray; import androidx.annotation.WorkerThread; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; @@ -78,6 +79,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private static final boolean DEBUG = false; private final BroadcastDispatcher mDispatcher; + private final Context mContext; private final AppOpsManager mAppOps; private final AudioManager mAudioManager; private final LocationManager mLocationManager; @@ -137,10 +139,11 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAudioManager = audioManager; mSensorPrivacyController = sensorPrivacyController; mMicMuted = audioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mPackageManager = context.getPackageManager(); + mContext = context; dumpManager.registerDumpable(TAG, this); } @@ -159,8 +162,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mSensorPrivacyController.addCallback(this); mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); @@ -357,6 +360,15 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return mLocationProviderPackages.contains(packageName); } + private boolean isSpeechRecognizerUsage(int opCode, String packageName) { + if (AppOpsManager.OP_RECORD_AUDIO != opCode) { + return false; + } + + return packageName.equals( + mContext.getString(R.string.config_systemSpeechRecognizer)); + } + // TODO ntmyren: remove after teamfood is finished private boolean shouldShowAppPredictor(String pkgName) { if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled", @@ -388,7 +400,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon } // TODO ntmyren: Replace this with more robust check if this moves beyond teamfood if ((appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName)) - || shouldShowAppPredictor(packageName)) { + || shouldShowAppPredictor(packageName) + || isSpeechRecognizerUsage(appOpCode, packageName)) { return true; } @@ -473,7 +486,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon @Override public void onOpNoted(int code, int uid, String packageName, - @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) { + String attributionTag, @AppOpsManager.OpFlags int flags, + @AppOpsManager.Mode int result) { if (DEBUG) { Log.w(TAG, "Noted op: " + code + " with result " + AppOpsManager.MODE_NAMES[result] + " for package " + packageName); @@ -582,16 +596,16 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon @Override public void onReceive(Context context, Intent intent) { mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); updateSensorDisabledStatus(); } @Override public void onSensorBlockedChanged(int sensor, boolean blocked) { mBGHandler.post(() -> { - if (sensor == INDIVIDUAL_SENSOR_CAMERA) { + if (sensor == CAMERA) { mCameraDisabled = blocked; - } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) { + } else if (sensor == MICROPHONE) { mMicMuted = mAudioManager.isMicrophoneMute() || blocked; } updateSensorDisabledStatus(); diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index e85cafaf47ac..d3168c8d2a05 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -170,7 +170,9 @@ public class AssistManager { boolean visible = false; if (mView != null) { visible = mView.isShowing(); - mWindowManager.removeView(mView); + if (mView.isAttachedToWindow()) { + mWindowManager.removeView(mView); + } } mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java index cc608ef87bc6..007080bc8603 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java @@ -16,17 +16,22 @@ package com.android.systemui.biometrics; +import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.graphics.Insets; import android.graphics.Rect; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.util.AttributeSet; import android.util.Log; +import android.view.Surface; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; /** @@ -51,53 +56,38 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView { mSensorProps = prop; } - /** - * For devices where the sensor is too high up, calculates the amount of padding necessary to - * move/center the biometric icon within the sensor's physical location. - */ - static int calculateBottomSpacerHeight(int displayHeightPx, int navbarHeightPx, - int dialogBottomMarginPx, @NonNull View buttonBar, @NonNull View textIndicator, - @NonNull FingerprintSensorPropertiesInternal sensorProperties) { - final int sensorDistanceFromBottom = displayHeightPx - sensorProperties.sensorLocationY - - sensorProperties.sensorRadius; - - final int spacerHeight = sensorDistanceFromBottom - - textIndicator.getMeasuredHeight() - - buttonBar.getMeasuredHeight() - - dialogBottomMarginPx - - navbarHeightPx; - - Log.d(TAG, "Display height: " + displayHeightPx - + ", Distance from bottom: " + sensorDistanceFromBottom - + ", Bottom margin: " + dialogBottomMarginPx - + ", Navbar height: " + navbarHeightPx - + ", Spacer height: " + spacerHeight); - - return spacerHeight; - } - @Override + @NonNull AuthDialog.LayoutParams onMeasureInternal(int width, int height) { - final View spaceBelowIcon = findViewById(R.id.space_below_icon); - spaceBelowIcon.setVisibility(View.VISIBLE); + final int displayRotation = getDisplay().getRotation(); + switch (displayRotation) { + case Surface.ROTATION_0: + return onMeasureInternalPortrait(width, height); + case Surface.ROTATION_90: + case Surface.ROTATION_270: + return onMeasureInternalLandscape(width, height); + default: + Log.e(TAG, "Unsupported display rotation: " + displayRotation); + return super.onMeasureInternal(width, height); + } + } + @NonNull + private AuthDialog.LayoutParams onMeasureInternalPortrait(int width, int height) { // Get the height of the everything below the icon. Currently, that's the indicator and - // button bar - final View textIndicator = findViewById(R.id.indicator); - final View buttonBar = findViewById(R.id.button_bar); + // button bar. + final int textIndicatorHeight = getViewHeightPx(R.id.indicator); + final int buttonBarHeight = getViewHeightPx(R.id.button_bar); // Figure out where the bottom of the sensor anim should be. // Navbar + dialogMargin + buttonBar + textIndicator + spacerHeight = sensorDistFromBottom - final int dialogBottomMarginPx = getResources() - .getDimensionPixelSize(R.dimen.biometric_dialog_border_padding); - final WindowManager wm = getContext().getSystemService(WindowManager.class); - final Rect bounds = wm.getCurrentWindowMetrics().getBounds(); - final int navbarHeight = wm.getCurrentWindowMetrics().getWindowInsets() - .getInsets(WindowInsets.Type.navigationBars()).toRect().height(); - final int displayHeight = bounds.height(); - - final int spacerHeight = calculateBottomSpacerHeight(displayHeight, navbarHeight, - dialogBottomMarginPx, buttonBar, textIndicator, mSensorProps); + final int dialogMargin = getDialogMarginPx(); + final WindowManager windowManager = getContext().getSystemService(WindowManager.class); + final int displayHeight = getWindowBounds(windowManager).height(); + final Insets navbarInsets = getNavbarInsets(windowManager); + final int bottomSpacerHeight = calculateBottomSpacerHeightForPortrait( + mSensorProps, displayHeight, textIndicatorHeight, buttonBarHeight, + dialogMargin, navbarInsets.bottom); // Go through each of the children and do the custom measurement. int totalHeight = 0; @@ -105,22 +95,25 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView { final int sensorDiameter = mSensorProps.sensorRadius * 2; for (int i = 0; i < numChildren; i++) { final View child = getChildAt(i); - if (child.getId() == R.id.biometric_icon_frame) { - // Create a frame that's exactly the size of the sensor circle - child.measure( - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.biometric_icon) { - // Icon should never be larger than the circle - child.measure( + final FrameLayout iconFrame = (FrameLayout) child; + final View icon = iconFrame.getChildAt(0); + + // Ensure that the icon is never larger than the sensor. + icon.measure( MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); + + // Create a frame that's exactly the height of the sensor circle. + iconFrame.measure( + MeasureSpec.makeMeasureSpec( + child.getLayoutParams().width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); } else if (child.getId() == R.id.space_above_icon) { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, - MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec( + child.getLayoutParams().height, MeasureSpec.EXACTLY)); } else if (child.getId() == R.id.button_bar) { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), @@ -128,8 +121,9 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView { MeasureSpec.EXACTLY)); } else if (child.getId() == R.id.space_below_icon) { // Set the spacer height so the fingerprint icon is on the physical sensor area - child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(spacerHeight, MeasureSpec.EXACTLY)); + child.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY)); } else { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), @@ -143,4 +137,184 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView { return new AuthDialog.LayoutParams(width, totalHeight); } + + @NonNull + private AuthDialog.LayoutParams onMeasureInternalLandscape(int width, int height) { + // Find the spacer height needed to vertically align the icon with the sensor. + final int titleHeight = getViewHeightPx(R.id.title); + final int subtitleHeight = getViewHeightPx(R.id.subtitle); + final int descriptionHeight = getViewHeightPx(R.id.description); + final int topSpacerHeight = getViewHeightPx(R.id.space_above_icon); + final int textIndicatorHeight = getViewHeightPx(R.id.indicator); + final int buttonBarHeight = getViewHeightPx(R.id.button_bar); + final WindowManager windowManager = getContext().getSystemService(WindowManager.class); + final Insets navbarInsets = getNavbarInsets(windowManager); + final int bottomSpacerHeight = calculateBottomSpacerHeightForLandscape(titleHeight, + subtitleHeight, descriptionHeight, topSpacerHeight, textIndicatorHeight, + buttonBarHeight, navbarInsets.bottom); + + // Find the spacer width needed to horizontally align the icon with the sensor. + final int displayWidth = getWindowBounds(windowManager).width(); + final int dialogMargin = getDialogMarginPx(); + final int horizontalInset = navbarInsets.left + navbarInsets.right; + final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape( + mSensorProps, displayWidth, dialogMargin, horizontalInset); + + final int sensorDiameter = mSensorProps.sensorRadius * 2; + final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth; + + int remeasuredHeight = 0; + final int numChildren = getChildCount(); + for (int i = 0; i < numChildren; i++) { + final View child = getChildAt(i); + if (child.getId() == R.id.biometric_icon_frame) { + final FrameLayout iconFrame = (FrameLayout) child; + final View icon = iconFrame.getChildAt(0); + + // Ensure that the icon is never larger than the sensor. + icon.measure( + MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); + + // Create a frame that's exactly the height of the sensor circle. + iconFrame.measure( + MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); + } else if (child.getId() == R.id.space_above_icon || child.getId() == R.id.button_bar) { + // Adjust the width of the top spacer and button bar while preserving their heights. + child.measure( + MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec( + child.getLayoutParams().height, MeasureSpec.EXACTLY)); + } else if (child.getId() == R.id.space_below_icon) { + // Adjust the bottom spacer height to align the fingerprint icon with the sensor. + child.measure( + MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY)); + } else { + // Use the remeasured width for all other child views. + child.measure( + MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + } + + if (child.getVisibility() != View.GONE) { + remeasuredHeight += child.getMeasuredHeight(); + } + } + + return new AuthDialog.LayoutParams(remeasuredWidth, remeasuredHeight); + } + + private int getViewHeightPx(@IdRes int viewId) { + final View view = findViewById(viewId); + return view != null ? view.getMeasuredHeight() : 0; + } + + private int getDialogMarginPx() { + return getResources().getDimensionPixelSize(R.dimen.biometric_dialog_border_padding); + } + + @NonNull + private static Insets getNavbarInsets(@Nullable WindowManager windowManager) { + return windowManager != null && windowManager.getCurrentWindowMetrics() != null + ? windowManager.getCurrentWindowMetrics().getWindowInsets() + .getInsets(WindowInsets.Type.navigationBars()) + : Insets.NONE; + } + + @NonNull + private static Rect getWindowBounds(@Nullable WindowManager windowManager) { + return windowManager != null && windowManager.getCurrentWindowMetrics() != null + ? windowManager.getCurrentWindowMetrics().getBounds() + : new Rect(); + } + + /** + * For devices in portrait orientation where the sensor is too high up, calculates the amount of + * padding necessary to center the biometric icon within the sensor's physical location. + */ + @VisibleForTesting + static int calculateBottomSpacerHeightForPortrait( + @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx, + int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx, + int navbarBottomInsetPx) { + + final int sensorDistanceFromBottom = displayHeightPx + - sensorProperties.sensorLocationY + - sensorProperties.sensorRadius; + + final int spacerHeight = sensorDistanceFromBottom + - textIndicatorHeightPx + - buttonBarHeightPx + - dialogMarginPx + - navbarBottomInsetPx; + + Log.d(TAG, "Display height: " + displayHeightPx + + ", Distance from bottom: " + sensorDistanceFromBottom + + ", Bottom margin: " + dialogMarginPx + + ", Navbar bottom inset: " + navbarBottomInsetPx + + ", Bottom spacer height (portrait): " + spacerHeight); + + return spacerHeight; + } + + /** + * For devices in landscape orientation where the sensor is too high up, calculates the amount + * of padding necessary to center the biometric icon within the sensor's physical location. + */ + @VisibleForTesting + static int calculateBottomSpacerHeightForLandscape(int titleHeightPx, int subtitleHeightPx, + int descriptionHeightPx, int topSpacerHeightPx, int textIndicatorHeightPx, + int buttonBarHeightPx, int navbarBottomInsetPx) { + + final int dialogHeightAboveIcon = titleHeightPx + + subtitleHeightPx + + descriptionHeightPx + + topSpacerHeightPx; + + final int dialogHeightBelowIcon = textIndicatorHeightPx + buttonBarHeightPx; + + final int bottomSpacerHeight = dialogHeightAboveIcon + - dialogHeightBelowIcon + - navbarBottomInsetPx; + + Log.d(TAG, "Title height: " + titleHeightPx + + ", Subtitle height: " + subtitleHeightPx + + ", Description height: " + descriptionHeightPx + + ", Top spacer height: " + topSpacerHeightPx + + ", Text indicator height: " + textIndicatorHeightPx + + ", Button bar height: " + buttonBarHeightPx + + ", Navbar bottom inset: " + navbarBottomInsetPx + + ", Bottom spacer height (landscape): " + bottomSpacerHeight); + + return bottomSpacerHeight; + } + + /** + * For devices in landscape orientation where the sensor is too left/right, calculates the + * amount of padding necessary to center the biometric icon within the sensor's physical + * location. + */ + @VisibleForTesting + static int calculateHorizontalSpacerWidthForLandscape( + @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx, + int dialogMarginPx, int navbarHorizontalInsetPx) { + + final int sensorDistanceFromEdge = displayWidthPx + - sensorProperties.sensorLocationY + - sensorProperties.sensorRadius; + + final int horizontalPadding = sensorDistanceFromEdge + - dialogMarginPx + - navbarHorizontalInsetPx; + + Log.d(TAG, "Display width: " + displayWidthPx + + ", Distance from edge: " + sensorDistanceFromEdge + + ", Dialog margin: " + dialogMarginPx + + ", Navbar horizontal inset: " + navbarHorizontalInsetPx + + ", Horizontal spacer width (landscape): " + horizontalPadding); + + return horizontalPadding; + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 18206efefe9a..d59a865e2add 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -742,6 +742,7 @@ public abstract class AuthBiometricView extends LinearLayout { * @param height Height to constrain the measurements to. * @return See {@link AuthDialog.LayoutParams} */ + @NonNull AuthDialog.LayoutParams onMeasureInternal(int width, int height) { int totalHeight = 0; final int numChildren = getChildCount(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 935f89343754..d05e9278762d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -32,8 +32,10 @@ import android.os.IBinder; import android.os.Looper; import android.os.UserManager; import android.util.Log; +import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -433,6 +435,29 @@ public class AuthContainerView extends LinearLayout + mConfig.mPromptInfo.getAuthenticators()); } + if (mBiometricView instanceof AuthBiometricUdfpsView) { + final int displayRotation = getDisplay().getRotation(); + switch (displayRotation) { + case Surface.ROTATION_0: + mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM); + setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); + break; + case Surface.ROTATION_90: + mPanelController.setPosition(AuthPanelController.POSITION_RIGHT); + setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); + break; + case Surface.ROTATION_270: + mPanelController.setPosition(AuthPanelController.POSITION_LEFT); + setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + break; + default: + Log.e(TAG, "Unsupported display rotation: " + displayRotation); + mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM); + setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); + break; + } + } + if (mConfig.mSkipIntro) { mContainerState = STATE_SHOWING; } else { @@ -476,6 +501,13 @@ public class AuthContainerView extends LinearLayout } } + private void setScrollViewGravity(int gravity) { + final FrameLayout.LayoutParams params = + (FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams(); + params.gravity = gravity; + mBiometricScrollView.setLayoutParams(params); + } + @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java index 11503fbd0b1d..fa50f895f83e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java @@ -18,6 +18,7 @@ package com.android.systemui.biometrics; import android.animation.AnimatorSet; import android.animation.ValueAnimator; +import android.annotation.IntDef; import android.content.Context; import android.graphics.Outline; import android.util.Log; @@ -27,10 +28,20 @@ import android.view.animation.AccelerateDecelerateInterpolator; import com.android.systemui.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Controls the back panel and its animations for the BiometricPrompt UI. */ public class AuthPanelController extends ViewOutlineProvider { + public static final int POSITION_BOTTOM = 1; + public static final int POSITION_LEFT = 2; + public static final int POSITION_RIGHT = 3; + + @IntDef({POSITION_BOTTOM, POSITION_LEFT, POSITION_RIGHT}) + @Retention(RetentionPolicy.SOURCE) + public @interface Position {} private static final String TAG = "BiometricPrompt/AuthPanelController"; private static final boolean DEBUG = false; @@ -38,6 +49,7 @@ public class AuthPanelController extends ViewOutlineProvider { private final Context mContext; private final View mPanelView; + @Position private int mPosition = POSITION_BOTTOM; private boolean mUseFullScreen; private int mContainerWidth; @@ -51,21 +63,44 @@ public class AuthPanelController extends ViewOutlineProvider { @Override public void getOutline(View view, Outline outline) { - final int left = (mContainerWidth - mContentWidth) / 2; - final int right = mContainerWidth - left; - - // If the content fits within the container, shrink the height to wrap the content. - // Otherwise, set the outline to be the display size minus the margin - the content within - // is scrollable. - final int top = mContentHeight < mContainerHeight - ? mContainerHeight - mContentHeight - mMargin - : mMargin; - - // TODO(b/139954942) Likely don't need to "+1" after we resolve the navbar styling. - final int bottom = mContainerHeight - mMargin + 1; + final int left = getLeftBound(mPosition); + final int right = left + mContentWidth; + + // If the content fits in the container, shrink the height to wrap it. Otherwise, expand to + // fill the display (minus the margin), since the content is scrollable. + final int top = getTopBound(mPosition); + final int bottom = Math.min(top + mContentHeight, mContainerHeight - mMargin); + outline.setRoundRect(left, top, right, bottom, mCornerRadius); } + private int getLeftBound(@Position int position) { + switch (position) { + case POSITION_BOTTOM: + return (mContainerWidth - mContentWidth) / 2; + case POSITION_LEFT: + return mMargin; + case POSITION_RIGHT: + return mContainerWidth - mContentWidth - mMargin; + default: + Log.e(TAG, "Unrecognized position: " + position); + return getLeftBound(POSITION_BOTTOM); + } + } + + private int getTopBound(@Position int position) { + switch (position) { + case POSITION_BOTTOM: + return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin); + case POSITION_LEFT: + case POSITION_RIGHT: + return Math.max((mContainerHeight - mContentHeight) / 2, mMargin); + default: + Log.e(TAG, "Unrecognized position: " + position); + return getTopBound(POSITION_BOTTOM); + } + } + public void setContainerDimensions(int containerWidth, int containerHeight) { if (DEBUG) { Log.v(TAG, "Container Width: " + containerWidth + " Height: " + containerHeight); @@ -74,6 +109,10 @@ public class AuthPanelController extends ViewOutlineProvider { mContainerHeight = containerHeight; } + public void setPosition(@Position int position) { + mPosition = position; + } + public void setUseFullScreen(boolean fullScreen) { mUseFullScreen = fullScreen; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java index 3ea8140427cb..a51b6fd16445 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java @@ -17,10 +17,13 @@ package com.android.systemui.biometrics; import android.content.Context; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.systemui.R; @@ -29,23 +32,32 @@ import com.android.systemui.R; * sensor area. */ public abstract class UdfpsAnimation extends Drawable { - abstract void updateColor(); + protected abstract void updateColor(); + protected abstract void onDestroy(); @NonNull protected final Context mContext; @NonNull protected final Drawable mFingerprintDrawable; + @Nullable private View mView; + private boolean mIlluminationShowing; public UdfpsAnimation(@NonNull Context context) { mContext = context; mFingerprintDrawable = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); + mFingerprintDrawable.mutate(); } public void onSensorRectUpdated(@NonNull RectF sensorRect) { - int margin = (int) (sensorRect.bottom - sensorRect.top) / 5; - mFingerprintDrawable.setBounds( - (int) sensorRect.left + margin, + final int margin = (int) sensorRect.height() / 8; + + final Rect bounds = new Rect((int) sensorRect.left + margin, (int) sensorRect.top + margin, (int) sensorRect.right - margin, (int) sensorRect.bottom - margin); + updateFingerprintIconBounds(bounds); + } + + protected void updateFingerprintIconBounds(@NonNull Rect bounds) { + mFingerprintDrawable.setBounds(bounds); } @Override @@ -53,6 +65,18 @@ public abstract class UdfpsAnimation extends Drawable { mFingerprintDrawable.setAlpha(alpha); } + public void setAnimationView(UdfpsAnimationView view) { + mView = view; + } + + boolean isIlluminationShowing() { + return mIlluminationShowing; + } + + void setIlluminationShowing(boolean showing) { + mIlluminationShowing = showing; + } + /** * @return The amount of padding that's needed on each side of the sensor, in pixels. */ @@ -66,4 +90,10 @@ public abstract class UdfpsAnimation extends Drawable { public int getPaddingY() { return 0; } + + protected void postInvalidateView() { + if (mView != null) { + mView.postInvalidate(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java index 5290986b2a1c..015a598e972b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java @@ -22,13 +22,14 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; -import android.util.Log; +import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.settingslib.Utils; import com.android.systemui.R; /** @@ -40,9 +41,13 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { private static final float SHADOW_RADIUS = 5.f; private static final float PROGRESS_BAR_RADIUS = 140.f; - @Nullable private RectF mSensorRect; + @NonNull private final Drawable mMovingTargetFpIcon; @NonNull private final Paint mSensorPaint; - private final int mNotificationShadeColor; + @NonNull private final Paint mBlueFill; + @NonNull private final Paint mBlueStroke;; + + @Nullable private RectF mSensorRect; + @Nullable private UdfpsEnrollHelper mEnrollHelper; UdfpsAnimationEnroll(@NonNull Context context) { super(context); @@ -53,23 +58,54 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK); mSensorPaint.setStyle(Paint.Style.FILL); - mNotificationShadeColor = Utils.getColorAttr(context, - android.R.attr.colorBackgroundFloating).getDefaultColor(); + mBlueFill = new Paint(0 /* flags */); + mBlueFill.setAntiAlias(true); + mBlueFill.setColor(context.getColor(R.color.udfps_moving_target_fill)); + mBlueFill.setStyle(Paint.Style.FILL); + + mBlueStroke = new Paint(0 /* flags */); + mBlueStroke.setAntiAlias(true); + mBlueStroke.setColor(context.getColor(R.color.udfps_moving_target_stroke)); + mBlueStroke.setStyle(Paint.Style.STROKE); + mBlueStroke.setStrokeWidth(12); + + mMovingTargetFpIcon = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); + mMovingTargetFpIcon.setTint(Color.WHITE); + mMovingTargetFpIcon.mutate(); + } + + void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { + mEnrollHelper = helper; } @Override - void updateColor() { + protected void updateColor() { mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); } @Override + protected void onDestroy() { + + } + + @Override public void onSensorRectUpdated(@NonNull RectF sensorRect) { super.onSensorRectUpdated(sensorRect); mSensorRect = sensorRect; } @Override + protected void updateFingerprintIconBounds(@NonNull Rect bounds) { + super.updateFingerprintIconBounds(bounds); + mMovingTargetFpIcon.setBounds(bounds); + } + + @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + final boolean isNightMode = (mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) != 0; if (!isNightMode) { @@ -78,6 +114,24 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { } } mFingerprintDrawable.draw(canvas); + + // Draw moving target + if (mEnrollHelper.isCenterEnrollmentComplete()) { + mFingerprintDrawable.setAlpha(64); + + canvas.save(); + final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint(); + canvas.translate(point.x, point.y); + if (mSensorRect != null) { + canvas.drawOval(mSensorRect, mBlueFill); + canvas.drawOval(mSensorRect, mBlueStroke); + } + + mMovingTargetFpIcon.draw(canvas); + canvas.restore(); + } else { + mFingerprintDrawable.setAlpha(255); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java index efc864ade5ff..ef7a34000841 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java @@ -34,12 +34,21 @@ public class UdfpsAnimationFpmOther extends UdfpsAnimation { } @Override - void updateColor() { + protected void updateColor() { + + } + + @Override + protected void onDestroy() { } @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + mFingerprintDrawable.draw(canvas); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java index 501de9df575b..5f268cfa8fa5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java @@ -23,7 +23,6 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.util.MathUtils; -import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -42,8 +41,8 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv private static final String TAG = "UdfpsAnimationKeyguard"; - @NonNull private final View mParent; @NonNull private final Context mContext; + @NonNull private final StatusBarStateController mStatusBarStateController; private final int mMaxBurnInOffsetX; private final int mMaxBurnInOffsetY; @@ -52,11 +51,11 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv private float mBurnInOffsetX; private float mBurnInOffsetY; - UdfpsAnimationKeyguard(@NonNull View parent, @NonNull Context context, + UdfpsAnimationKeyguard(@NonNull Context context, @NonNull StatusBarStateController statusBarStateController) { super(context); - mParent = parent; mContext = context; + mStatusBarStateController = statusBarStateController; mMaxBurnInOffsetX = context.getResources() .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); @@ -73,10 +72,10 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv mInterpolatedDarkAmount); mBurnInOffsetY = MathUtils.lerp(0f, getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - - 0.5f * mMaxBurnInOffsetY, + - mMaxBurnInOffsetY, mInterpolatedDarkAmount); updateColor(); - mParent.postInvalidate(); + postInvalidateView(); } @Override @@ -92,6 +91,10 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + canvas.save(); canvas.translate(mBurnInOffsetX, mBurnInOffsetY); mFingerprintDrawable.draw(canvas); @@ -109,11 +112,16 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv } @Override - public void updateColor() { + protected void updateColor() { final int lockScreenIconColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); final int ambientDisplayIconColor = Color.WHITE; mFingerprintDrawable.setTint(ColorUtils.blendARGB(lockScreenIconColor, ambientDisplayIconColor, mInterpolatedDarkAmount)); } + + @Override + protected void onDestroy() { + mStatusBarStateController.removeCallback(this); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index 41ea4d66f575..43ecf6778022 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -20,40 +20,52 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Canvas; +import android.graphics.PointF; import android.graphics.RectF; import android.util.AttributeSet; -import android.view.View; +import android.widget.FrameLayout; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.statusbar.phone.StatusBar; /** - * Class that coordinates non-HBM animations (such as enroll, keyguard, BiometricPrompt, - * FingerprintManager). + * Base class for views containing UDFPS animations. Note that this is a FrameLayout so that we + * can support multiple child views drawing on the same region around the sensor location. */ -public class UdfpsAnimationView extends View implements DozeReceiver, +public abstract class UdfpsAnimationView extends FrameLayout implements DozeReceiver, StatusBar.ExpansionChangedListener { private static final String TAG = "UdfpsAnimationView"; + @Nullable protected abstract UdfpsAnimation getUdfpsAnimation(); + @NonNull private UdfpsView mParent; - @Nullable private UdfpsAnimation mUdfpsAnimation; @NonNull private RectF mSensorRect; private int mAlpha; public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mSensorRect = new RectF(); + setWillNotDraw(false); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - if (mUdfpsAnimation != null) { + if (getUdfpsAnimation() != null) { final int alpha = mParent.shouldPauseAuth() ? mAlpha : 255; - mUdfpsAnimation.setAlpha(alpha); - mUdfpsAnimation.draw(canvas); + getUdfpsAnimation().setAlpha(alpha); + getUdfpsAnimation().draw(canvas); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().onDestroy(); } } @@ -69,31 +81,46 @@ public class UdfpsAnimationView extends View implements DozeReceiver, return (int) ((1 - percent) * 255); } - void setParent(@NonNull UdfpsView parent) { - mParent = parent; + void onIlluminationStarting() { + if (getUdfpsAnimation() == null) { + return; + } + + getUdfpsAnimation().setIlluminationShowing(true); + postInvalidate(); } - void setAnimation(@Nullable UdfpsAnimation animation) { - mUdfpsAnimation = animation; + void onIlluminationStopped() { + if (getUdfpsAnimation() == null) { + return; + } + + getUdfpsAnimation().setIlluminationShowing(false); + postInvalidate(); + } + + void setParent(@NonNull UdfpsView parent) { + mParent = parent; } void onSensorRectUpdated(@NonNull RectF sensorRect) { mSensorRect = sensorRect; - if (mUdfpsAnimation != null) { - mUdfpsAnimation.onSensorRectUpdated(mSensorRect); + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().onSensorRectUpdated(mSensorRect); } } void updateColor() { - if (mUdfpsAnimation != null) { - mUdfpsAnimation.updateColor(); + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().updateColor(); } + postInvalidate(); } @Override public void dozeTimeTick() { - if (mUdfpsAnimation instanceof DozeReceiver) { - ((DozeReceiver) mUdfpsAnimation).dozeTimeTick(); + if (getUdfpsAnimation() instanceof DozeReceiver) { + ((DozeReceiver) getUdfpsAnimation()).dozeTimeTick(); } } @@ -104,16 +131,26 @@ public class UdfpsAnimationView extends View implements DozeReceiver, } public int getPaddingX() { - if (mUdfpsAnimation == null) { + if (getUdfpsAnimation() == null) { return 0; } - return mUdfpsAnimation.getPaddingX(); + return getUdfpsAnimation().getPaddingX(); } public int getPaddingY() { - if (mUdfpsAnimation == null) { + if (getUdfpsAnimation() == null) { return 0; } - return mUdfpsAnimation.getPaddingY(); + return getUdfpsAnimation().getPaddingY(); + } + + /** + * @return the amount of translation needed if the view currently requires the user to touch + * somewhere other than the exact center of the sensor. For example, this can happen + * during guided enrollment. + */ + @NonNull + PointF getTouchTranslation() { + return new PointF(0, 0); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java new file mode 100644 index 000000000000..515b442b61f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java @@ -0,0 +1,42 @@ +/* + * 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.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +/** + * Class that coordinates non-HBM animations during BiometricPrompt. + * + * Note that {@link AuthBiometricUdfpsView} also shows UDFPS animations. At some point we should + * de-dupe this if necessary. This will probably happen once the top-level TODO in UdfpsController + * is completed (inflate operation-specific views, instead of inflating generic udfps_view and + * adding operation-specific animations to it). + */ +public class UdfpsAnimationViewBp extends UdfpsAnimationView { + public UdfpsAnimationViewBp(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java new file mode 100644 index 000000000000..543df33dd5d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java @@ -0,0 +1,96 @@ +/* + * 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.biometrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PointF; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.android.systemui.R; + +/** + * Class that coordinates non-HBM animations during enrollment. + */ +public class UdfpsAnimationViewEnroll extends UdfpsAnimationView + implements UdfpsEnrollHelper.Listener { + + private static final String TAG = "UdfpsAnimationViewEnroll"; + + @NonNull private UdfpsAnimationEnroll mUdfpsAnimation; + @NonNull private UdfpsProgressBar mProgressBar; + @Nullable private UdfpsEnrollHelper mEnrollHelper; + + @NonNull + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mUdfpsAnimation; + } + + public UdfpsAnimationViewEnroll(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mUdfpsAnimation = new UdfpsAnimationEnroll(context); + } + + public void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { + mEnrollHelper = helper; + mUdfpsAnimation.setEnrollHelper(helper); + } + + @Override + protected void onFinishInflate() { + mProgressBar = findViewById(R.id.progress_bar); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mEnrollHelper == null) { + Log.e(TAG, "Enroll helper is null"); + return; + } + + if (mEnrollHelper.shouldShowProgressBar()) { + mProgressBar.setVisibility(View.VISIBLE); + + // Only need enrollment updates if the progress bar is showing :) + mEnrollHelper.setListener(this); + } + } + + @Override + public void onEnrollmentProgress(int remaining, int totalSteps) { + final int interpolatedProgress = mProgressBar.getMax() + * Math.max(0, totalSteps + 1 - remaining) / (totalSteps + 1); + + mProgressBar.setProgress(interpolatedProgress, true); + } + + @NonNull + @Override + PointF getTouchTranslation() { + if (!mEnrollHelper.isCenterEnrollmentComplete()) { + return new PointF(0, 0); + } else { + return mEnrollHelper.getNextGuidedEnrollmentPoint(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java new file mode 100644 index 000000000000..3d2f5a0fe5cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java @@ -0,0 +1,42 @@ +/* + * 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.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +/** + * Class that coordinates non-HBM animations during other usage of FingerprintManager (not + * including Keyguard). + */ +public class UdfpsAnimationViewFpmOther extends UdfpsAnimationView { + + private final UdfpsAnimationFpmOther mAnimation; + + public UdfpsAnimationViewFpmOther(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mAnimation = new UdfpsAnimationFpmOther(context); + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mAnimation; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java new file mode 100644 index 000000000000..7d0b3e59feb1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java @@ -0,0 +1,49 @@ +/* + * 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.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; + +/** + * Class that coordinates non-HBM animations during keyguard authentication. + */ +public class UdfpsAnimationViewKeyguard extends UdfpsAnimationView { + @Nullable private UdfpsAnimationKeyguard mAnimation; + + public UdfpsAnimationViewKeyguard(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + void setStatusBarStateController(@NonNull StatusBarStateController statusBarStateController) { + if (mAnimation == null) { + mAnimation = new UdfpsAnimationKeyguard(getContext(), statusBarStateController); + mAnimation.setAnimationView(this); + } + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mAnimation; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index e7b08e72877d..4b6a8f639cc4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -20,7 +20,10 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; @@ -28,11 +31,15 @@ import android.graphics.RectF; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.SystemClock; +import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; +import android.os.RemoteException; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; +import android.view.VelocityTracker; import android.view.WindowManager; import androidx.annotation.NonNull; @@ -44,7 +51,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -67,24 +73,31 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private static final String TAG = "UdfpsController"; private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; + // Minimum required delay between consecutive touch logs in milliseconds. + private static final long MIN_TOUCH_LOG_INTERVAL = 50; + private final Context mContext; private final FingerprintManager mFingerprintManager; + @NonNull private final LayoutInflater mInflater; private final WindowManager mWindowManager; private final DelayableExecutor mFgExecutor; - private final StatusBarStateController mStatusBarStateController; + @NonNull private final StatusBar mStatusBar; + @NonNull private final StatusBarStateController mStatusBarStateController; // 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; private final WindowManager.LayoutParams mCoreLayoutParams; - private final UdfpsView mView; - // Indicates whether the overlay is currently showing. Even if it has been requested, it might - // not be showing. - private boolean mIsOverlayShowing; - // Indicates whether the overlay has been requested. - private boolean mIsOverlayRequested; - // Reason the overlay has been requested. See IUdfpsOverlayController for definitions. - private int mRequestReason; - @Nullable UdfpsEnrollHelper mEnrollHelper; + + // Tracks the velocity of a touch to help filter out the touches that move too fast. + @Nullable private VelocityTracker mVelocityTracker; + // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. + private int mActivePointerId; + // The timestamp of the most recent touch log. + private long mTouchLogTime; + + @Nullable private UdfpsView mView; + // The current request from FingerprintService. Null if no current request. + @Nullable ServerRequest mServerRequest; // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when // to turn off high brightness mode. To get around this limitation, the state of the AOD @@ -93,83 +106,219 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private boolean mIsAodInterruptActive; @Nullable private Runnable mCancelAodTimeoutAction; + /** + * Keeps track of state within a single FingerprintService request. Note that this state + * persists across configuration changes, etc, since it is considered a single request. + * + * TODO: Perhaps we can move more global variables into here + */ + private static class ServerRequest { + // Reason the overlay has been requested. See IUdfpsOverlayController for definitions. + final int mRequestReason; + @NonNull final IUdfpsOverlayControllerCallback mCallback; + @Nullable final UdfpsEnrollHelper mEnrollHelper; + + ServerRequest(int requestReason, @NonNull IUdfpsOverlayControllerCallback callback, + @Nullable UdfpsEnrollHelper enrollHelper) { + mRequestReason = requestReason; + mCallback = callback; + mEnrollHelper = enrollHelper; + } + + void onEnrollmentProgress(int remaining) { + if (mEnrollHelper != null) { + mEnrollHelper.onEnrollmentProgress(remaining); + } + } + + void onEnrollmentHelp() { + if (mEnrollHelper != null) { + mEnrollHelper.onEnrollmentHelp(); + } + } + + void onUserCanceled() { + try { + mCallback.onUserCanceled(); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + } + public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override - public void showUdfpsOverlay(int sensorId, int reason) { + public void showUdfpsOverlay(int sensorId, int reason, + @NonNull IUdfpsOverlayControllerCallback callback) { + final UdfpsEnrollHelper enrollHelper; if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) { - mEnrollHelper = new UdfpsEnrollHelper(reason); + enrollHelper = new UdfpsEnrollHelper(mContext, reason); } else { - mEnrollHelper = null; + enrollHelper = null; } - UdfpsController.this.showOverlay(reason); + + mServerRequest = new ServerRequest(reason, callback, enrollHelper); + updateOverlay(); } @Override public void hideUdfpsOverlay(int sensorId) { - UdfpsController.this.hideOverlay(); + mServerRequest = null; + updateOverlay(); } @Override public void onEnrollmentProgress(int sensorId, int remaining) { - mView.onEnrollmentProgress(remaining); + if (mServerRequest == null) { + Log.e(TAG, "onEnrollProgress received but serverRequest is null"); + return; + } + mServerRequest.onEnrollmentProgress(remaining); } @Override public void onEnrollmentHelp(int sensorId) { - mView.onEnrollmentHelp(); + if (mServerRequest == null) { + Log.e(TAG, "onEnrollmentHelp received but serverRequest is null"); + return; + } + mServerRequest.onEnrollmentHelp(); } @Override public void setDebugMessage(int sensorId, String message) { + if (mView == null) { + return; + } mView.setDebugMessage(message); } } + @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = + (expansion, expanded) -> mView.onExpansionChanged(expansion, expanded); + + @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStateChanged(int newState) { + mView.onStateChanged(newState); + } + }; + + private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { + final float vx = tracker.getXVelocity(pointerId); + final float vy = tracker.getYVelocity(pointerId); + return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mServerRequest != null + && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { + Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received"); + mServerRequest.onUserCanceled(); + mServerRequest = null; + updateOverlay(); + } + } + }; + @SuppressLint("ClickableViewAccessibility") - private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> { - UdfpsView view = (UdfpsView) v; - final boolean isFingerDown = view.isIlluminationRequested(); - switch (event.getAction()) { + private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> { + UdfpsView udfpsView = (UdfpsView) view; + final boolean isFingerDown = udfpsView.isIlluminationRequested(); + boolean handled = false; + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + // To simplify the lifecycle of the velocity tracker, make sure it's never null + // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } else { + // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new + // ACTION_DOWN, in that case we should just reuse the old instance. + mVelocityTracker.clear(); + } + // TODO: move isWithinSensorArea to UdfpsController. + if (udfpsView.isWithinSensorArea(event.getX(), event.getY())) { + // 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. + mActivePointerId = event.getPointerId(0); + mVelocityTracker.addMovement(event); + handled = true; + } + break; + case MotionEvent.ACTION_MOVE: - final boolean isValidTouch = view.isValidTouch(event.getX(), event.getY(), - event.getPressure()); - if (!isFingerDown && isValidTouch) { - onFingerDown((int) event.getX(), (int) event.getY(), event.getTouchMinor(), - event.getTouchMajor()); - } else if (isFingerDown && !isValidTouch) { - onFingerUp(); + final int idx = event.findPointerIndex(mActivePointerId); + if (idx == event.getActionIndex()) { + final float x = event.getX(idx); + final float y = event.getY(idx); + if (udfpsView.isWithinSensorArea(x, y)) { + mVelocityTracker.addMovement(event); + // Compute pointer velocity in pixels per second. + mVelocityTracker.computeCurrentVelocity(1000); + // Compute pointer speed from X and Y velocities. + final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); + final float minor = event.getTouchMinor(idx); + final float major = event.getTouchMajor(idx); + final String touchInfo = String.format("minor: %.1f, major: %.1f, v: %.1f", + minor, major, v); + final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime; + if (!isFingerDown) { + onFingerDown((int) x, (int) y, minor, major); + Log.v(TAG, "onTouch | finger down: " + touchInfo); + mTouchLogTime = SystemClock.elapsedRealtime(); + handled = true; + } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { + Log.v(TAG, "onTouch | finger move: " + touchInfo); + mTouchLogTime = SystemClock.elapsedRealtime(); + } + } else if (isFingerDown) { + Log.v(TAG, "onTouch | finger outside"); + onFingerUp(); + } } - return true; + break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } if (isFingerDown) { + Log.v(TAG, "onTouch | finger up"); onFingerUp(); } - return true; + break; default: - return false; + // Do nothing. } + return handled; }; @Inject public UdfpsController(@NonNull Context context, @Main Resources resources, - LayoutInflater inflater, + @NonNull LayoutInflater inflater, @Nullable FingerprintManager fingerprintManager, WindowManager windowManager, @NonNull StatusBarStateController statusBarStateController, @Main DelayableExecutor fgExecutor, - @Nullable StatusBar statusBar) { + @NonNull StatusBar statusBar) { mContext = context; + mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the // fingerprint manager should never be null. mFingerprintManager = checkNotNull(fingerprintManager); mWindowManager = windowManager; mFgExecutor = fgExecutor; + mStatusBar = statusBar; mStatusBarStateController = statusBarStateController; mSensorProps = findFirstUdfps(); @@ -181,6 +330,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { WindowManager.LayoutParams.TYPE_BOOT_PROGRESS, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); mCoreLayoutParams.setTitle(TAG); @@ -190,15 +340,11 @@ public class UdfpsController implements DozeReceiver, HbmCallback { WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; - mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false); - mView.setSensorProperties(mSensorProps); - mView.setHbmCallback(this); - - statusBar.addExpansionChangedListener(mView); - statusBarStateController.addCallback(mView); - mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController()); - mIsOverlayShowing = false; + + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.registerReceiver(mBroadcastReceiver, filter); } @Nullable @@ -214,6 +360,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @Override public void dozeTimeTick() { + if (mView == null) { + return; + } mView.dozeTimeTick(); } @@ -221,36 +370,24 @@ public class UdfpsController implements DozeReceiver, HbmCallback { * @return where the UDFPS exists on the screen in pixels. */ public RectF getSensorLocation() { - return mView.getSensorRect(); - } - - private void showOverlay(int reason) { - if (mIsOverlayRequested) { - return; - } - mIsOverlayRequested = true; - mRequestReason = reason; - updateOverlay(); - } - - private void hideOverlay() { - if (!mIsOverlayRequested) { - return; - } - mIsOverlayRequested = false; - mRequestReason = IUdfpsOverlayController.REASON_UNKNOWN; - updateOverlay(); + // This is currently used to calculate the amount of space available for notifications + // on lockscreen. Keyguard is only shown in portrait mode for now, so this will need to + // be updated if that ever changes. + return new RectF(mSensorProps.sensorLocationX - mSensorProps.sensorRadius, + mSensorProps.sensorLocationY - mSensorProps.sensorRadius, + mSensorProps.sensorLocationX + mSensorProps.sensorRadius, + mSensorProps.sensorLocationY + mSensorProps.sensorRadius); } private void updateOverlay() { - if (mIsOverlayRequested) { - showUdfpsOverlay(mRequestReason); + if (mServerRequest != null) { + showUdfpsOverlay(mServerRequest.mRequestReason); } else { hideUdfpsOverlay(); } } - private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimation animation) { + private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimationView animation) { final int paddingX = animation != null ? animation.getPaddingX() : 0; final int paddingY = animation != null ? animation.getPaddingY() : 0; @@ -299,14 +436,25 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private void showUdfpsOverlay(int reason) { mFgExecutor.execute(() -> { - if (!mIsOverlayShowing) { + if (mView == null) { try { Log.v(TAG, "showUdfpsOverlay | adding window"); - final UdfpsAnimation animation = getUdfpsAnimationForReason(reason); - mView.setExtras(animation, mEnrollHelper); + // TODO: Eventually we should refactor the code to inflate an + // operation-specific view here, instead of inflating a generic udfps_view + // and adding operation-specific animations to it. + mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false); + mView.setSensorProperties(mSensorProps); + mView.setHbmCallback(this); + + final UdfpsAnimationView animation = getUdfpsAnimationViewForReason(reason); + mView.setAnimationView(animation); + + mStatusBar.addExpansionChangedListener(mStatusBarExpansionListener); + mStatusBarStateController.addCallback(mStatusBarStateListener); + mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState()); + mWindowManager.addView(mView, computeLayoutParams(animation)); mView.setOnTouchListener(mOnTouchListener); - mIsOverlayShowing = true; } catch (RuntimeException e) { Log.e(TAG, "showUdfpsOverlay | failed to add window", e); } @@ -316,17 +464,40 @@ public class UdfpsController implements DozeReceiver, HbmCallback { }); } - @Nullable - private UdfpsAnimation getUdfpsAnimationForReason(int reason) { + @NonNull + private UdfpsAnimationView getUdfpsAnimationViewForReason(int reason) { Log.d(TAG, "getUdfpsAnimationForReason: " + reason); + + final LayoutInflater inflater = LayoutInflater.from(mContext); + switch (reason) { case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR: - case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: - return new UdfpsAnimationEnroll(mContext); - case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: - return new UdfpsAnimationKeyguard(mView, mContext, mStatusBarStateController); - case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: - return new UdfpsAnimationFpmOther(mContext); + case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: { + final UdfpsAnimationViewEnroll view = (UdfpsAnimationViewEnroll) + inflater.inflate(R.layout.udfps_animation_view_enroll, null, false); + view.setEnrollHelper(mServerRequest.mEnrollHelper); + return view; + } + + case IUdfpsOverlayController.REASON_AUTH_BP: { + final UdfpsAnimationViewBp view = (UdfpsAnimationViewBp) + inflater.inflate(R.layout.udfps_animation_view_bp, null, false); + return view; + } + + case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: { + final UdfpsAnimationViewKeyguard view = (UdfpsAnimationViewKeyguard) + inflater.inflate(R.layout.udfps_animation_view_keyguard, null, false); + view.setStatusBarStateController(mStatusBarStateController); + return view; + } + + case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: { + final UdfpsAnimationViewFpmOther view = (UdfpsAnimationViewFpmOther) + inflater.inflate(R.layout.udfps_animation_view_fpm_other, null, false); + return view; + } + default: Log.d(TAG, "Animation for reason " + reason + " not supported yet"); return null; @@ -335,14 +506,16 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private void hideUdfpsOverlay() { mFgExecutor.execute(() -> { - if (mIsOverlayShowing) { + if (mView != null) { Log.v(TAG, "hideUdfpsOverlay | removing window"); - mView.setExtras(null, null); - mView.setOnTouchListener(null); // Reset the controller back to its starting state. onFingerUp(); + + mStatusBar.removeExpansionChangedListener(mStatusBarExpansionListener); + mStatusBarStateController.removeCallback(mStatusBarStateListener); + mWindowManager.removeView(mView); - mIsOverlayShowing = false; + mView = null; } else { Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden"); } @@ -389,12 +562,20 @@ public class UdfpsController implements DozeReceiver, HbmCallback { // This method can be called from the UI thread. private void onFingerDown(int x, int y, float minor, float major) { + if (mView == null) { + Log.w(TAG, "Null view in onFingerDown"); + return; + } mView.startIllumination(() -> mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major)); } // This method can be called from the UI thread. private void onFingerUp() { + if (mView == null) { + Log.w(TAG, "Null view in onFingerUp"); + return; + } mFingerprintManager.onPointerUp(mSensorProps.sensorId); mView.stopIllumination(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 2442633a4a69..14eca1b1cb2c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -16,9 +16,15 @@ package com.android.systemui.biometrics; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PointF; import android.hardware.fingerprint.IUdfpsOverlayController; +import android.util.TypedValue; -import androidx.annotation.NonNull; +import java.util.ArrayList; +import java.util.List; /** * Helps keep track of enrollment state and animates the progress bar accordingly. @@ -26,35 +32,96 @@ import androidx.annotation.NonNull; public class UdfpsEnrollHelper { private static final String TAG = "UdfpsEnrollHelper"; + // Enroll with two center touches before going to guided enrollment + private static final int NUM_CENTER_TOUCHES = 2; + + interface Listener { + void onEnrollmentProgress(int remaining, int totalSteps); + } + // IUdfpsOverlayController reason private final int mEnrollReason; + private final List<PointF> mGuidedEnrollmentPoints; private int mTotalSteps = -1; - private int mCurrentProgress = 0; + private int mRemainingSteps = -1; + + // Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the + // interface makes no promises about monotonically increasing by one each time. + private int mLocationsEnrolled = 0; + + @Nullable Listener mListener; - public UdfpsEnrollHelper(int reason) { + public UdfpsEnrollHelper(@NonNull Context context, int reason) { mEnrollReason = reason; + mGuidedEnrollmentPoints = new ArrayList<>(); + + // Number of pixels per mm + float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1, + context.getResources().getDisplayMetrics()); + + mGuidedEnrollmentPoints.add(new PointF( 2.00f * px, 0.00f * px)); + mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px)); + mGuidedEnrollmentPoints.add(new PointF( 0.88f * px, 2.70f * px)); + mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px)); + mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.29f * px, 4.92f * px)); + mGuidedEnrollmentPoints.add(new PointF( 3.82f * px, 1.78f * px)); } boolean shouldShowProgressBar() { return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING; } - void onEnrollmentProgress(int remaining, @NonNull UdfpsProgressBar progressBar) { + void onEnrollmentProgress(int remaining) { if (mTotalSteps == -1) { mTotalSteps = remaining; } - mCurrentProgress = progressBar.getMax() * Math.max(0, mTotalSteps + 1 - remaining) - / (mTotalSteps + 1); - progressBar.setProgress(mCurrentProgress, true /* animate */); - } + if (remaining != mRemainingSteps) { + mLocationsEnrolled++; + } + + mRemainingSteps = remaining; - void updateProgress(@NonNull UdfpsProgressBar progressBar) { - progressBar.setProgress(mCurrentProgress); + if (mListener != null) { + mListener.onEnrollmentProgress(remaining, mTotalSteps); + } } void onEnrollmentHelp() { } + + void setListener(@NonNull Listener listener) { + mListener = listener; + + // Only notify during setListener if enrollment is already in progress, so the progress + // bar can be updated. If enrollment has not started yet, the progress bar will be empty + // anyway. + if (mTotalSteps != -1) { + mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps); + } + } + + boolean isCenterEnrollmentComplete() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + final int stepsEnrolled = mTotalSteps - mRemainingSteps; + return stepsEnrolled >= NUM_CENTER_TOUCHES; + } + + @NonNull + PointF getNextGuidedEnrollmentPoint() { + final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES; + return mGuidedEnrollmentPoints.get(index % mGuidedEnrollmentPoints.size()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java index 97c215e1d75f..61ec127ee946 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java @@ -52,6 +52,12 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { public UdfpsSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); + // Make this SurfaceView draw on top of everything else in this window. This allows us to + // 1) Always show the HBM circle on top of everything else, and + // 2) Properly composite this view with any other animations in the same window no matter + // what contents are added in which order to this view hierarchy. + setZOrderOnTop(true); + mHolder = getHolder(); mHolder.setFormat(PixelFormat.RGBA_8888); @@ -61,7 +67,9 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { mSensorPaint.setARGB(255, 255, 255, 255); mSensorPaint.setStyle(Paint.Style.FILL); - mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint); + mIlluminationDotDrawable = canvas -> { + canvas.drawOval(mSensorRect, mSensorPaint); + }; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 00cb28b8b8fb..a52bddc1dcd5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -27,12 +27,13 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.PointF; import android.graphics.RectF; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; @@ -51,12 +52,11 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private static final int DEBUG_TEXT_SIZE_PX = 32; - @NonNull private final UdfpsSurfaceView mHbmSurfaceView; - @NonNull private final UdfpsAnimationView mAnimationView; @NonNull private final RectF mSensorRect; @NonNull private final Paint mDebugTextPaint; - @Nullable private UdfpsProgressBar mProgressBar; + @NonNull private UdfpsSurfaceView mHbmSurfaceView; + @Nullable private UdfpsAnimationView mAnimationView; // Used to obtain the sensor location. @NonNull private FingerprintSensorPropertiesInternal mSensorProps; @@ -66,7 +66,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private boolean mIlluminationRequested; private int mStatusBarState; private boolean mNotificationShadeExpanded; - @Nullable private UdfpsEnrollHelper mEnrollHelper; public UdfpsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -84,19 +83,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin a.recycle(); } - // Inflate UdfpsSurfaceView - final LayoutInflater inflater = LayoutInflater.from(context); - mHbmSurfaceView = (UdfpsSurfaceView) inflater.inflate(R.layout.udfps_surface_view, - null, false); - addView(mHbmSurfaceView); - mHbmSurfaceView.setVisibility(View.INVISIBLE); - - // Inflate UdfpsAnimationView - mAnimationView = (UdfpsAnimationView) inflater.inflate(R.layout.udfps_animation_view, - null, false); - mAnimationView.setParent(this); - addView(mAnimationView); - mSensorRect = new RectF(); mDebugTextPaint = new Paint(); @@ -107,21 +93,28 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin mIlluminationRequested = false; } + // Don't propagate any touch events to the child views. + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + + @Override + protected void onFinishInflate() { + mHbmSurfaceView = findViewById(R.id.hbm_view); + } + void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) { mSensorProps = properties; } - void setExtras(@Nullable UdfpsAnimation animation, @Nullable UdfpsEnrollHelper enrollHelper) { - mAnimationView.setAnimation(animation); - mEnrollHelper = enrollHelper; + void setAnimationView(@NonNull UdfpsAnimationView animation) { + mAnimationView = animation; + animation.setParent(this); - if (enrollHelper != null) { - mEnrollHelper.updateProgress(mProgressBar); - mProgressBar.setVisibility(enrollHelper.shouldShowProgressBar() - ? View.VISIBLE : View.GONE); - } else { - mProgressBar.setVisibility(View.GONE); - } + // TODO: Consider using a ViewStub placeholder to maintain positioning and inflating it + // after the animation type has been decided. + addView(animation, 0); } @Override @@ -131,27 +124,24 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void dozeTimeTick() { + if (mAnimationView == null) { + return; + } mAnimationView.dozeTimeTick(); } @Override - public void onExpandedChanged(boolean isExpanded) { - mNotificationShadeExpanded = isExpanded; - } - - @Override public void onStateChanged(int newState) { mStatusBarState = newState; } @Override public void onExpansionChanged(float expansion, boolean expanded) { - mAnimationView.onExpansionChanged(expansion, expanded); - } + mNotificationShadeExpanded = expanded; - @Override - protected void onFinishInflate() { - mProgressBar = findViewById(R.id.progress_bar); + if (mAnimationView != null) { + mAnimationView.onExpansionChanged(expansion, expanded); + } } @Override @@ -191,19 +181,16 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin } } - RectF getSensorRect() { - return new RectF(mSensorRect); - } - void setDebugMessage(String message) { mDebugMessage = message; postInvalidate(); } - boolean isValidTouch(float x, float y, float pressure) { + boolean isWithinSensorArea(float x, float y) { // The X and Y coordinates of the sensor's center. - final float cx = mSensorRect.centerX(); - final float cy = mSensorRect.centerY(); + final PointF translation = mAnimationView.getTouchTranslation(); + final float cx = mSensorRect.centerX() + translation.x; + final float cy = mSensorRect.centerY() + translation.y; // Radii along the X and Y axes. final float rx = (mSensorRect.right - mSensorRect.left) / 2.0f; final float ry = (mSensorRect.bottom - mSensorRect.top) / 2.0f; @@ -236,7 +223,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void startIllumination(@Nullable Runnable onIlluminatedRunnable) { mIlluminationRequested = true; - mAnimationView.setVisibility(View.INVISIBLE); + mAnimationView.onIlluminationStarting(); mHbmSurfaceView.setVisibility(View.VISIBLE); mHbmSurfaceView.startIllumination(onIlluminatedRunnable); } @@ -244,16 +231,8 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void stopIllumination() { mIlluminationRequested = false; - mAnimationView.setVisibility(View.VISIBLE); + mAnimationView.onIlluminationStopped(); mHbmSurfaceView.setVisibility(View.INVISIBLE); mHbmSurfaceView.stopIllumination(); } - - void onEnrollmentProgress(int remaining) { - mEnrollHelper.onEnrollmentProgress(remaining, mProgressBar); - } - - void onEnrollmentHelp() { - - } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java index 64576a97ddb2..f7fe14a8e841 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java @@ -22,7 +22,6 @@ import static com.android.systemui.classifier.FalsingModule.DOUBLE_TAP_TOUCH_SLO import android.view.MotionEvent; import java.util.List; -import java.util.Queue; import javax.inject.Inject; import javax.inject.Named; @@ -49,8 +48,7 @@ public class DoubleTapClassifier extends FalsingClassifier { @Override Result calculateFalsingResult(double historyPenalty, double historyConfidence) { List<MotionEvent> secondTapEvents = getRecentMotionEvents(); - Queue<? extends List<MotionEvent>> historicalEvents = getHistoricalEvents(); - List<MotionEvent> firstTapEvents = historicalEvents.peek(); + List<MotionEvent> firstTapEvents = getPriorMotionEvents(); StringBuilder reason = new StringBuilder(); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java index dbfeacfa91c4..f40045b0f08e 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java @@ -21,7 +21,6 @@ import android.view.MotionEvent; import com.android.systemui.util.sensors.ProximitySensor; import java.util.List; -import java.util.Queue; /** * Base class for rules that determine False touches. @@ -40,8 +39,8 @@ public abstract class FalsingClassifier { return mDataProvider.getRecentMotionEvents(); } - Queue<? extends List<MotionEvent>> getHistoricalEvents() { - return mDataProvider.getHistoricalMotionEvents(); + List<MotionEvent> getPriorMotionEvents() { + return mDataProvider.getPriorMotionEvents(); } MotionEvent getFirstMotionEvent() { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 4bacc1598490..336f13f117d3 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -23,13 +23,9 @@ import android.view.MotionEvent.PointerProperties; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.util.time.SystemClock; import java.util.ArrayList; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; -import java.util.Queue; import javax.inject.Inject; @@ -40,24 +36,23 @@ import javax.inject.Inject; public class FalsingDataProvider { private static final long MOTION_EVENT_AGE_MS = 1000; - private static final long EXTENDED_MOTION_EVENT_AGE_MS = 30 * 1000; private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI); private final int mWidthPixels; private final int mHeightPixels; private final BatteryController mBatteryController; - private final SystemClock mSystemClock; private final float mXdpi; private final float mYdpi; private final List<SessionListener> mSessionListeners = new ArrayList<>(); private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>(); - private final List<GestureCompleteListener> mGestuerCompleteListeners = new ArrayList<>(); + private final List<GestureCompleteListener> mGestureCompleteListeners = new ArrayList<>(); private @Classifier.InteractionType int mInteractionType; - private final Deque<TimeLimitedMotionEventBuffer> mExtendedMotionEvents = new LinkedList<>(); private TimeLimitedMotionEventBuffer mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS); + private List<MotionEvent> mPriorMotionEvents; + private boolean mDirty = true; private float mAngle = 0; @@ -66,14 +61,12 @@ public class FalsingDataProvider { private boolean mJustUnlockedWithFace; @Inject - public FalsingDataProvider(DisplayMetrics displayMetrics, BatteryController batteryController, - SystemClock systemClock) { + public FalsingDataProvider(DisplayMetrics displayMetrics, BatteryController batteryController) { mXdpi = displayMetrics.xdpi; mYdpi = displayMetrics.ydpi; mWidthPixels = displayMetrics.widthPixels; mHeightPixels = displayMetrics.heightPixels; mBatteryController = batteryController; - mSystemClock = systemClock; FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi()); FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels()); @@ -111,10 +104,10 @@ public class FalsingDataProvider { private void completePriorGesture() { if (!mRecentMotionEvents.isEmpty()) { - mGestuerCompleteListeners.forEach(listener -> listener.onGestureComplete( + mGestureCompleteListeners.forEach(listener -> listener.onGestureComplete( mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime())); - mExtendedMotionEvents.addFirst(mRecentMotionEvents); + mPriorMotionEvents = mRecentMotionEvents; } } @@ -140,14 +133,8 @@ public class FalsingDataProvider { return mRecentMotionEvents; } - /** Returns recent gestures, exclusive of the most recent gesture. Newer gestures come first. */ - public Queue<? extends List<MotionEvent>> getHistoricalMotionEvents() { - long nowMs = mSystemClock.uptimeMillis(); - - mExtendedMotionEvents.removeIf( - motionEvents -> motionEvents.isFullyExpired(nowMs - EXTENDED_MOTION_EVENT_AGE_MS)); - - return mExtendedMotionEvents; + public List<MotionEvent> getPriorMotionEvents() { + return mPriorMotionEvents; } /** @@ -344,12 +331,12 @@ public class FalsingDataProvider { /** Register a {@link GestureCompleteListener}. */ public void addGestureCompleteListener(GestureCompleteListener listener) { - mGestuerCompleteListeners.add(listener); + mGestureCompleteListeners.add(listener); } /** Unregister a {@link GestureCompleteListener}. */ public void removeGestureCompleteListener(GestureCompleteListener listener) { - mGestuerCompleteListeners.remove(listener); + mGestureCompleteListeners.remove(listener); } void onSessionStarted() { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java index 7969b4e83ac0..e5da38936593 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java @@ -42,18 +42,6 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> { mMotionEvents = new LinkedList<>(); } - /** - * Returns true if the most recent event in the buffer is past the expiration time. - * - * This method does not mutate the underlying data. This method does imply that, if the supplied - * expiration time is old enough and a new {@link MotionEvent} gets added to the buffer, all - * prior events would be removed. - */ - public boolean isFullyExpired(long expirationMs) { - return mMotionEvents.isEmpty() - || mMotionEvents.getLast().getEventTime() <= expirationMs; - } - private void ejectOldEvents() { if (mMotionEvents.isEmpty()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt index 36937d622b5b..6b7a1ac8cb7e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt @@ -91,7 +91,7 @@ class ControlsComponent @Inject constructor( } /** - * @return true if controls are feature-enabled and have available services to serve controls + * @return true if controls are feature-enabled and the user has the setting enabled */ fun isEnabled() = featureEnabled && lazyControlsController.get().available diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt index cad166d7cd9e..1ea1d97cace5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt @@ -41,7 +41,7 @@ import com.android.systemui.controls.ui.ControlsUiController object ControlsAnimations { - private const val ALPHA_EXIT_DURATION = 167L + private const val ALPHA_EXIT_DURATION = 183L private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 1c2f17c55671..2d647a907b17 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -313,6 +313,10 @@ class ControlsFavoritingActivity @Inject constructor( setOnClickListener { val i = Intent().apply { component = ComponentName(context, ControlsProviderSelectorActivity::class.java) + putExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + backToGlobalActions + ) } if (doneButton.isEnabled) { // The user has made changes diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 08147746a4c8..d5e41d031eac 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -32,6 +32,8 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent @@ -49,13 +51,15 @@ class ControlsProviderSelectorActivity @Inject constructor( private val listingController: ControlsListingController, private val controlsController: ControlsController, private val globalActionsComponent: GlobalActionsComponent, - broadcastDispatcher: BroadcastDispatcher + private val broadcastDispatcher: BroadcastDispatcher, + private val uiController: ControlsUiController ) : LifecycleActivity() { companion object { private const val TAG = "ControlsProviderSelectorActivity" } + private var backToGlobalActions = true private lateinit var recyclerView: RecyclerView private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = listingController.currentUserId @@ -101,10 +105,19 @@ class ControlsProviderSelectorActivity @Inject constructor( } } requireViewById<View>(R.id.done).visibility = View.GONE + + backToGlobalActions = intent.getBooleanExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + true + ) } override fun onBackPressed() { - globalActionsComponent.handleShowGlobalActionsMenu() + if (backToGlobalActions) { + globalActionsComponent.handleShowGlobalActionsMenu() + } else { + ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + } animateExitAndFinish() } @@ -152,8 +165,13 @@ class ControlsProviderSelectorActivity @Inject constructor( listingController.getAppLabel(it)) putExtra(Intent.EXTRA_COMPONENT_NAME, it) putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true) + putExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + backToGlobalActions + ) } startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) + animateExitAndFinish() } } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index 2b362b94d1f5..242c6afebc3e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -21,7 +21,9 @@ import android.app.Activity; import com.android.systemui.ForegroundServicesDialog; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; +import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.screenrecord.ScreenRecordDialog; +import com.android.systemui.screenshot.LongScreenshotActivity; import com.android.systemui.settings.brightness.BrightnessDialog; import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity; import com.android.systemui.tuner.TunerActivity; @@ -99,4 +101,16 @@ public abstract class DefaultActivityBinder { @IntoMap @ClassKey(PeopleSpaceActivity.class) public abstract Activity bindPeopleSpaceActivity(PeopleSpaceActivity activity); + + /** Inject into LongScreenshotActivity. */ + @Binds + @IntoMap + @ClassKey(LongScreenshotActivity.class) + public abstract Activity bindLongScreenshotActivity(LongScreenshotActivity activity); + + /** Inject into LaunchConversationActivity. */ + @Binds + @IntoMap + @ClassKey(LaunchConversationActivity.class) + public abstract Activity bindLaunchConversationActivity(LaunchConversationActivity activity); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 726e2d06bf90..a2f96bbad203 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.om.OverlayManager; import android.hardware.display.AmbientDisplayConfiguration; +import android.hardware.display.ColorDisplayManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -60,6 +61,7 @@ import com.android.systemui.navigationbar.NavigationBarOverlayController; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.PluginInitializerImpl; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.UserTracker; @@ -82,6 +84,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.theme.ThemeOverlayApplier; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -266,6 +269,15 @@ public class DependencyProvider { } /** */ + @SysUISingleton + @Provides + public ReduceBrightColorsController provideReduceBrightColorsListener( + @Background Handler bgHandler, UserTracker userTracker, + ColorDisplayManager colorDisplayManager, SecureSettings secureSettings) { + return new ReduceBrightColorsController(userTracker, bgHandler, + colorDisplayManager, secureSettings); + } + @Provides @SysUISingleton public ActivityManagerWrapper provideActivityManagerWrapper() { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 04c4e977b2cf..1c5715c0296d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -25,6 +25,7 @@ import android.app.IActivityTaskManager; import android.app.IWallpaperManager; import android.app.KeyguardManager; import android.app.NotificationManager; +import android.app.StatsManager; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.app.role.RoleManager; @@ -321,6 +322,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static StatsManager provideStatsManager(Context context) { + return context.getSystemService(StatsManager.class); + } + + @Provides + @Singleton @Nullable static TelecomManager provideTelecomManager(Context context) { return context.getSystemService(TelecomManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index ffb8446f3e21..8f79de518419 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -33,6 +33,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.transition.RemoteTransitions; import java.util.Optional; @@ -88,6 +89,9 @@ public interface SysUIComponent { @BindsInstance Builder setTransitions(RemoteTransitions t); + @BindsInstance + Builder setStartingSurface(Optional<StartingSurface> s); + SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index ec3188a6144f..1ed881993800 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -27,8 +27,8 @@ import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.media.systemsounds.HomeSoundEffectController; -import com.android.systemui.people.widget.PeopleSpaceWidgetEnabler; import com.android.systemui.power.PowerUI; +import com.android.systemui.privacy.television.TvOngoingPrivacyChip; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsModule; import com.android.systemui.shortcut.ShortcutKeyDispatcher; @@ -155,6 +155,12 @@ public abstract class SystemUIBinder { @ClassKey(TvNotificationPanel.class) public abstract SystemUI bindsTvNotificationPanel(TvNotificationPanel sysui); + /** Inject into TvOngoingPrivacyChip. */ + @Binds + @IntoMap + @ClassKey(TvOngoingPrivacyChip.class) + public abstract SystemUI bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui); + /** Inject into VolumeUI. */ @Binds @IntoMap @@ -178,10 +184,4 @@ public abstract class SystemUIBinder { @IntoMap @ClassKey(HomeSoundEffectController.class) public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui); - - /** Inject into PeopleSpaceWidgetEnabler. */ - @Binds - @IntoMap - @ClassKey(PeopleSpaceWidgetEnabler.class) - public abstract SystemUI bindPeopleSpaceWidgetEnabler(PeopleSpaceWidgetEnabler sysui); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index f3726a37bb65..1b77d1c16639 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -32,6 +32,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.transition.RemoteTransitions; import java.util.Optional; @@ -98,4 +99,7 @@ public interface WMComponent { @WMSingleton RemoteTransitions getTransitions(); + + @WMSingleton + Optional<StartingSurface> getStartingSurface(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 8ab135ced97e..4418696bfc9b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -32,6 +32,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.AlarmTimeout; @@ -41,12 +42,15 @@ import java.util.Calendar; import javax.inject.Inject; +import dagger.Lazy; + /** * The policy controlling doze. */ @DozeScope public class DozeUi implements DozeMachine.Part, TunerService.Tunable { - + // if enabled, calls dozeTimeTick() whenever the time changes: + private static final boolean BURN_IN_TESTING_ENABLED = false; private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min private final Context mContext; private final DozeHost mHost; @@ -57,16 +61,28 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { private final boolean mCanAnimateTransition; private final DozeParameters mDozeParameters; private final DozeLog mDozeLog; + private final Lazy<StatusBarStateController> mStatusBarStateController; private boolean mKeyguardShowing; private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback = new KeyguardUpdateMonitorCallback() { - @Override public void onKeyguardVisibilityChanged(boolean showing) { mKeyguardShowing = showing; updateAnimateScreenOff(); } + + @Override + public void onTimeChanged() { + if (BURN_IN_TESTING_ENABLED && mStatusBarStateController != null + && mStatusBarStateController.get().isDozing()) { + // update whenever the time changes for manual burn in testing + mHost.dozeTimeTick(); + + // Keep wakelock until a frame has been pushed. + mHandler.post(mWakeLock.wrap(() -> {})); + } + } }; private long mLastTimeTickElapsed = 0; @@ -75,7 +91,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { public DozeUi(Context context, AlarmManager alarmManager, WakeLock wakeLock, DozeHost host, @Main Handler handler, DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor, - DozeLog dozeLog, TunerService tunerService) { + DozeLog dozeLog, TunerService tunerService, + Lazy<StatusBarStateController> statusBarStateController) { mContext = context; mWakeLock = wakeLock; mHost = host; @@ -85,8 +102,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler); keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); mDozeLog = dozeLog; - tunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON); + mStatusBarStateController = statusBarStateController; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index d85b10167697..1f67276bfbae 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -79,7 +79,6 @@ import android.transition.AutoTransition; import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.ArraySet; -import android.util.FeatureFlagUtils; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.IWindowManager; @@ -114,7 +113,6 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.EmergencyAffordanceManager; -import com.android.internal.util.ScreenRecordHelper; import com.android.internal.util.ScreenshotHelper; import com.android.internal.view.RotationPolicy; import com.android.internal.widget.LockPatternUtils; @@ -242,7 +240,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final boolean mShowSilentToggle; private final EmergencyAffordanceManager mEmergencyAffordanceManager; private final ScreenshotHelper mScreenshotHelper; - private final ScreenRecordHelper mScreenRecordHelper; private final ActivityStarter mActivityStarter; private final SysuiColorExtractor mSysuiColorExtractor; private final IStatusBarService mStatusBarService; @@ -378,7 +375,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); mScreenshotHelper = new ScreenshotHelper(context); - mScreenRecordHelper = new ScreenRecordHelper(context); mConfigurationController.addCallback(this); @@ -979,7 +975,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } @VisibleForTesting - class ScreenshotAction extends SinglePressAction implements LongPressAction { + class ScreenshotAction extends SinglePressAction { final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons"; public ScreenshotAction() { @@ -1024,18 +1020,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger( com.android.internal.R.integer.config_navBarInteractionMode); } - - - @Override - public boolean onLongPress() { - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) { - mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS); - mScreenRecordHelper.launchRecordPrompt(); - } else { - onPress(); - } - return true; - } } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index d4678f39e404..127128dda112 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -103,6 +103,11 @@ public class KeyguardIndicationRotateTextViewController extends */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean showImmediately) { + if (type == INDICATION_TYPE_NOW_PLAYING + || type == INDICATION_TYPE_REVERSE_CHARGING) { + // temporarily don't show here, instead use AmbientContainer b/181049781 + return; + } final boolean hasPreviousIndication = mIndicationMessages.get(type) != null; final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt index bd3f5a6d82a5..7fb7d8b0eaa5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt @@ -20,9 +20,13 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.text.TextUtils +import android.util.Log import com.android.settingslib.media.MediaOutputConstants import javax.inject.Inject +private const val TAG = "MediaOutputDlgReceiver" +private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + /** * BroadcastReceiver for handling media output intent */ @@ -32,8 +36,13 @@ class MediaOutputDialogReceiver @Inject constructor( override fun onReceive(context: Context, intent: Intent) { if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action)) { - mediaOutputDialogFactory.create( - intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME), false) + val packageName: String? = + intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME) + if (!TextUtils.isEmpty(packageName)) { + mediaOutputDialogFactory.create(packageName!!, false) + } else if (DEBUG) { + Log.e(TAG, "Unable to launch media output dialog. Package name is empty.") + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 27ea64f85b11..4491cc12a3cb 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -25,7 +25,6 @@ import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -54,7 +53,6 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -116,7 +114,7 @@ public class NavigationBarController implements Callbacks, // Tracks config changes that will actually recreate the nav bar private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS + | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_UI_MODE); @Inject @@ -171,6 +169,7 @@ public class NavigationBarController implements Callbacks, configurationController.addCallback(this); mConfigChanges.applyNewConfig(mContext.getResources()); mNavBarOverlayController = navBarOverlayController; + mNavigationModeController.addListener(this); } @Override @@ -181,24 +180,25 @@ public class NavigationBarController implements Callbacks, } } else { for (int i = 0; i < mNavigationBars.size(); i++) { - mNavigationBars.get(i).onConfigurationChanged(newConfig); + mNavigationBars.valueAt(i).onConfigurationChanged(newConfig); } } } @Override public void onNavigationModeChanged(int mode) { - // Workaround for b/132825155, for secondary users, we currently don't receive configuration - // changes on overlay package change since SystemUI runs for the system user. In this case, - // trigger a new configuration change to ensure that the nav bar is updated in the same way. - int userId = ActivityManagerWrapper.getInstance().getCurrentUserId(); - if (userId != UserHandle.USER_SYSTEM) { - mHandler.post(() -> { - for (int i = 0; i < mNavigationBars.size(); i++) { - recreateNavigationBar(mNavigationBars.keyAt(i)); + mHandler.post(() -> { + for (int i = 0; i < mNavigationBars.size(); i++) { + NavigationBar navBar = mNavigationBars.valueAt(i); + if (navBar == null) { + continue; } - }); - } + NavigationBarView view = (NavigationBarView) navBar.getView(); + if (view != null) { + view.updateStates(); + } + } + }); } @Override @@ -399,7 +399,7 @@ public class NavigationBarController implements Callbacks, if (i > 0) { pw.println(); } - mNavigationBars.get(i).dump(pw); + mNavigationBars.valueAt(i).dump(pw); } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java index c526c5d59552..62b9458447d8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java @@ -21,6 +21,7 @@ import android.content.Context; import android.view.View; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.FeatureFlags; import java.util.function.Consumer; @@ -31,16 +32,22 @@ import javax.inject.Inject; public class NavigationBarOverlayController { protected final Context mContext; + protected final FeatureFlags mFeatureFlags; @Inject - public NavigationBarOverlayController(Context context) { + public NavigationBarOverlayController(Context context, FeatureFlags featureFlags) { mContext = context; + mFeatureFlags = featureFlags; } public Context getContext() { return mContext; } + public boolean isNavigationBarOverlayEnabled() { + return mFeatureFlags.isNavigationBarOverlayEnabled(); + } + /** * Initialize the controller with visibility change callback and light/dark icon color. */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java index b55fa4d612f9..61e1d61e7909 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java @@ -192,6 +192,7 @@ public final class NavigationBarTransitions extends BarTransitions implements buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity); } 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 8e75bec72c15..35d5ca949f85 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -171,6 +171,7 @@ public class NavigationBarView extends FrameLayout implements private NotificationPanelViewController mPanelView; private FloatingRotationButton mFloatingRotationButton; private RotationButtonController mRotationButtonController; + private NavigationBarOverlayController mNavBarOverlayController; /** * Helper that is responsible for showing the right toast when a disallowed activity operation @@ -278,9 +279,11 @@ public class NavigationBarView extends FrameLayout implements return; } + // When in gestural and the IME is showing, don't use the nearest region since it will take + // gesture space away from the IME info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set(getButtonLocations(false /* includeFloatingRotationButton */, - false /* inScreen */)); + false /* inScreen */, false /* useNearestRegion */)); }; private final Consumer<Boolean> mRotationButtonListener = (visible) -> { @@ -339,8 +342,11 @@ public class NavigationBarView extends FrameLayout implements isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton, mRotationButtonListener); - Dependency.get(NavigationBarOverlayController.class).init( - mNavbarOverlayVisibilityChangeCallback, mLightIconColor, mDarkIconColor); + mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.init( + mNavbarOverlayVisibilityChangeCallback, mLightIconColor, mDarkIconColor); + } mConfiguration = new Configuration(); mTmpLastConfiguration = new Configuration(); @@ -431,8 +437,9 @@ public class NavigationBarView extends FrameLayout implements // The visibility of the navigation bar buttons is dependent on the transient state of // the navigation bar. - Dependency.get(NavigationBarOverlayController.class).setButtonState( - isTransient, /* force */ false); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.setButtonState(isTransient, /* force */ false); + } } void onBarTransition(int newMode) { @@ -666,7 +673,9 @@ public class NavigationBarView extends FrameLayout implements } mImeVisible = visible; mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible); - Dependency.get(NavigationBarOverlayController.class).setCanShow(!mImeVisible); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.setCanShow(!mImeVisible); + } } public void setDisabledFlags(int disabledFlags) { @@ -974,7 +983,8 @@ public class NavigationBarView extends FrameLayout implements */ public void notifyActiveTouchRegions() { mOverviewProxyService.onActiveNavBarRegionChanges( - getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */)); + getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */, + true /* useNearestRegion */)); } private void updateButtonTouchRegionCache() { @@ -985,36 +995,49 @@ public class NavigationBarView extends FrameLayout implements .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions(); } + /** + * @param includeFloatingRotationButton Whether to include the floating rotation button in the + * region for all the buttons + * @param inScreenSpace Whether to return values in screen space or window space + * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds + * @return + */ private Region getButtonLocations(boolean includeFloatingRotationButton, - boolean inScreenSpace) { + boolean inScreenSpace, boolean useNearestRegion) { + if (useNearestRegion && !inScreenSpace) { + // We currently don't support getting the nearest region in anything but screen space + useNearestRegion = false; + } mTmpRegion.setEmpty(); updateButtonTouchRegionCache(); - updateButtonLocation(getBackButton(), inScreenSpace); - updateButtonLocation(getHomeButton(), inScreenSpace); - updateButtonLocation(getRecentsButton(), inScreenSpace); - updateButtonLocation(getImeSwitchButton(), inScreenSpace); - updateButtonLocation(getAccessibilityButton(), inScreenSpace); + updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion); if (includeFloatingRotationButton && mFloatingRotationButton.isVisible()) { + // Note: this button is floating so the nearest region doesn't apply updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace); } else { - updateButtonLocation(getRotateSuggestionButton(), inScreenSpace); + updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion); } - final NavigationBarOverlayController navBarButtonsController = - Dependency.get(NavigationBarOverlayController.class); - if (navBarButtonsController.isVisible()) { - updateButtonLocation(navBarButtonsController.getCurrentView(), inScreenSpace); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled() + && mNavBarOverlayController.isVisible()) { + // Note: this button is floating so the nearest region doesn't apply + updateButtonLocation(mNavBarOverlayController.getCurrentView(), inScreenSpace); } return mTmpRegion; } - private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace) { + private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace, + boolean useNearestRegion) { View view = button.getCurrentView(); if (view == null || !button.isVisible()) { return; } // If the button is tappable from perspective of NearestTouchFrame, then we'll // include the regions where the tap is valid instead of just the button layout location - if (mButtonFullTouchableRegions.containsKey(view)) { + if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) { mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION); return; } @@ -1230,7 +1253,9 @@ public class NavigationBarView extends FrameLayout implements if (mRotationButtonController != null) { mRotationButtonController.registerListeners(); } - Dependency.get(NavigationBarOverlayController.class).registerListeners(); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.registerListeners(); + } getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); updateNavButtonIcons(); @@ -1247,7 +1272,10 @@ public class NavigationBarView extends FrameLayout implements if (mRotationButtonController != null) { mRotationButtonController.unregisterListeners(); } - Dependency.get(NavigationBarOverlayController.class).unregisterListeners(); + + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.unregisterListeners(); + } mEdgeBackGestureHandler.onNavBarDetached(); getViewTreeObserver().removeOnComputeInternalInsetsListener( diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java index 65d4e23f7734..422ffd524aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java @@ -132,7 +132,7 @@ public class NavigationModeController implements Dumpable { Settings.Secure.putString(mCurrentUserContext.getContentResolver(), Secure.NAVIGATION_MODE, String.valueOf(mode))); if (DEBUG) { - Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode); + Log.d(TAG, "updateCurrentInteractionMode: mode=" + mode); dumpAssetPaths(mCurrentUserContext); } @@ -200,7 +200,7 @@ public class NavigationModeController implements Dumpable { Log.d(TAG, " assetPaths="); ApkAssets[] assets = context.getResources().getAssets().getApkAssets(); for (ApkAssets a : assets) { - Log.d(TAG, " " + a.getAssetPath()); + Log.d(TAG, " " + a.getDebugName()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java index b1c85b5c530e..8ff7b7ae8e59 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java @@ -84,19 +84,14 @@ public class NearestTouchFrame extends FrameLayout { } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); mClickableChildren.clear(); mAttachedChildren.clear(); mTouchableRegions.clear(); addClickableChildren(this); - cacheClosestChildLocations(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); getLocationInWindow(mOffset); + cacheClosestChildLocations(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 292cc7a7deaa..9e3be69c414e 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -93,6 +93,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( "gestures.back_timeout", 250); + private static final int MAX_NUM_LOGGED_PREDICTIONS = 10; + private static final int MAX_NUM_LOGGED_GESTURES = 10; + // Temporary log until b/176302696 is resolved static final boolean DEBUG_MISSING_GESTURE = true; static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; @@ -222,8 +225,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private String mPackageName; private float mMLResults; - private static final int MAX_LOGGED_PREDICTIONS = 10; + // For debugging private ArrayDeque<String> mPredictionLog = new ArrayDeque<>(); + private ArrayDeque<String> mGestureLog = new ArrayDeque<>(); private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; @@ -605,29 +609,33 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa if (mVocab != null) { app = mVocab.getOrDefault(mPackageName, -1); } - // Check if we are within the tightest bounds beyond which - // we would not need to run the ML model. - boolean withinRange = x <= mMLEnableWidth + mLeftInset - || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset); - if (!withinRange) { + + // Denotes whether we should proceed with the gesture. Even if it is false, we may want to + // log it assuming it is not invalid due to exclusion. + boolean withinRange = x < mEdgeWidthLeft + mLeftInset + || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset); + if (withinRange) { int results = -1; - if (mUseMLModel && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) { - withinRange = results == 1; - } else { - // Denotes whether we should proceed with the gesture. - // Even if it is false, we may want to log it assuming - // it is not invalid due to exclusion. - withinRange = x <= mEdgeWidthLeft + mLeftInset - || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset); + + // Check if we are within the tightest bounds beyond which we would not need to run the + // ML model + boolean withinMinRange = x < mMLEnableWidth + mLeftInset + || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset); + if (!withinMinRange && mUseMLModel + && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) { + withinRange = (results == 1); } } // For debugging purposes - if (mPredictionLog.size() >= MAX_LOGGED_PREDICTIONS) { + if (mPredictionLog.size() >= MAX_NUM_LOGGED_PREDICTIONS) { mPredictionLog.removeFirst(); } - mPredictionLog.addLast(String.format("[%d,%d,%d,%f,%d]", - x, y, app, mMLResults, withinRange ? 1 : 0)); + mPredictionLog.addLast(String.format("Prediction [%d,%d,%d,%d,%f,%d]", + System.currentTimeMillis(), x, y, app, mMLResults, withinRange ? 1 : 0)); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, mPredictionLog.peekLast()); + } // Always allow if the user is in a transient sticky immersive state if (mIsNavBarShownTransiently) { @@ -689,6 +697,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private void onMotionEvent(MotionEvent ev) { int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev); + } + // Verify if this is in within the touch region and we aren't in immersive mode, and // either the bouncer is showing or the notification panel is hidden mInputEventReceiver.setBatchingEnabled(false); @@ -709,6 +721,19 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mEndPoint.set(-1, -1); mThresholdCrossed = false; } + + // For debugging purposes + if (mGestureLog.size() >= MAX_NUM_LOGGED_GESTURES) { + mGestureLog.removeFirst(); + } + mGestureLog.addLast(String.format( + "Gesture [%d,alw=%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]", + System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge, mIsBackGestureAllowed, + QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize, + mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, mGestureLog.peekLast()); + } } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { mEndPoint.x = (int) ev.getX(); @@ -827,18 +852,29 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa public void dump(PrintWriter pw) { pw.println("EdgeBackGestureHandler:"); pw.println(" mIsEnabled=" + mIsEnabled); + pw.println(" mIsAttached=" + mIsAttached); pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); + pw.println(" mIsGesturalModeEnabled=" + mIsGesturalModeEnabled); + pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); + pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning); pw.println(" mAllowGesture=" + mAllowGesture); + pw.println(" mUseMLModel=" + mUseMLModel); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); pw.println(" mInRejectedExclusion" + mInRejectedExclusion); pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); - pw.println(" mIsAttached=" + mIsAttached); + pw.println(" mPipExcludedBounds=" + mPipExcludedBounds); pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); - pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); - pw.println(" mPredictionLog=" + String.join(";", mPredictionLog)); + pw.println(" mLeftInset=" + mLeftInset); + pw.println(" mRightInset=" + mRightInset); + pw.println(" mMLEnableWidth=" + mMLEnableWidth); + pw.println(" mMLModelThreshold=" + mMLModelThreshold); + pw.println(" mTouchSlop=" + mTouchSlop); + pw.println(" mBottomGestureHeight=" + mBottomGestureHeight); + pw.println(" mPredictionLog=" + String.join("\n", mPredictionLog)); + pw.println(" mGestureLog=" + String.join("\n", mGestureLog)); pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 2f9b17aece8e..a69ec278be91 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -19,13 +19,10 @@ package com.android.systemui.people; import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; -import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle; - import android.app.Activity; import android.app.INotificationManager; import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; -import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; @@ -36,12 +33,10 @@ import android.provider.Settings; import android.util.Log; import android.view.ViewGroup; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.R; +import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import java.util.Collections; import java.util.List; import javax.inject.Inject; @@ -56,15 +51,14 @@ public class PeopleSpaceActivity extends Activity { private ViewGroup mPeopleSpaceLayout; private IPeopleManager mPeopleManager; + private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; private INotificationManager mNotificationManager; private PackageManager mPackageManager; private LauncherApps mLauncherApps; private Context mContext; - private AppWidgetManager mAppWidgetManager; private NotificationEntryManager mNotificationEntryManager; private int mAppWidgetId; private boolean mShowSingleConversation; - private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); @Inject public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager) { @@ -83,8 +77,8 @@ public class PeopleSpaceActivity extends Activity { mPackageManager = getPackageManager(); mPeopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); + mPeopleSpaceWidgetManager = new PeopleSpaceWidgetManager(mContext); mLauncherApps = mContext.getSystemService(LauncherApps.class); - mAppWidgetManager = AppWidgetManager.getInstance(mContext); setTileViewsWithPriorityConversations(); mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); @@ -144,29 +138,13 @@ public class PeopleSpaceActivity extends Activity { + mAppWidgetId); } } - - PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId); - int[] widgetIds = new int[mAppWidgetId]; - // TODO: Populate new widget with existing conversation notification, if there is any. - PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, - mPeopleManager); - if (mLauncherApps != null) { - try { - if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId()); - mLauncherApps.cacheShortcuts(tile.getPackageName(), - Collections.singletonList(tile.getId()), - getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); - } catch (Exception e) { - Log.w(TAG, "Exception caching shortcut:" + e); - } - } + mPeopleSpaceWidgetManager.addNewWidget(tile, mAppWidgetId); finishActivity(); } /** Finish activity with a successful widget configuration result. */ private void finishActivity() { if (DEBUG) Log.d(TAG, "Widget added!"); - mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED); setActivityResult(RESULT_OK); finish(); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java index 9ae7847031aa..6f89332e66a9 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.pm.LauncherApps; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; -import android.os.UserHandle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -82,7 +81,7 @@ public class PeopleSpaceTileView extends LinearLayout { public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) { mTileView.setOnClickListener(v -> launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null, - UserHandle.getUserHandleForUid(tile.getUid()))); + tile.getUserHandle())); } /** Sets the click listener of the tile directly. */ diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index cd1131ba3e79..502c95c47d03 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -16,6 +16,7 @@ package com.android.systemui.people; +import static android.app.Notification.CATEGORY_MISSED_CALL; import static android.app.Notification.EXTRA_MESSAGES; import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY; import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY; @@ -54,7 +55,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.ServiceManager; -import android.os.UserHandle; import android.provider.ContactsContract; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; @@ -159,7 +159,6 @@ public class PeopleSpaceUtils { List<ConversationChannelWrapper> conversations = notificationManager.getConversations( false).getList(); - // Add priority conversations to tiles list. Stream<ShortcutInfo> priorityConversations = conversations.stream() .filter(c -> c.getNotificationChannel() != null @@ -189,7 +188,7 @@ public class PeopleSpaceUtils { tiles.addAll(recentTiles); } - tiles = augmentTilesFromVisibleNotifications(tiles, notificationEntryManager); + tiles = augmentTilesFromVisibleNotifications(context, tiles, notificationEntryManager); return tiles; } @@ -221,7 +220,7 @@ public class PeopleSpaceUtils { } @Nullable - private static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, + public static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, AppWidgetManager appWidgetManager, Context context, int appWidgetId) { try { @@ -231,7 +230,7 @@ public class PeopleSpaceUtils { String pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); int userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); String shortcutId = widgetSp.getString(SHORTCUT_ID, EMPTY_STRING); - if (pkg.isEmpty() || shortcutId.isEmpty() || userId == INVALID_WIDGET_ID) { + if (!validKey(shortcutId, pkg, userId)) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); shortcutId = sp.getString(String.valueOf(appWidgetId), null); if (shortcutId == null) { @@ -251,7 +250,11 @@ public class PeopleSpaceUtils { } // If tile is null, we need to retrieve from persisted storage. - if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots"); + if (DEBUG) { + Log.d(TAG, + "Retrieving from storage after reboots: " + shortcutId + " user: " + userId + + " pkg: " + pkg); + } LauncherApps launcherApps = context.getSystemService(LauncherApps.class); ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId); if (channel == null) { @@ -265,6 +268,17 @@ public class PeopleSpaceUtils { } } + /** Returns stored widgets for the conversation specified. */ + public static Set<String> getStoredWidgetIds(SharedPreferences sp, String shortcutId, + String packageName, int userId) { + if (shortcutId == null || packageName == null) { + return new HashSet<>(); + } + String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); + return new HashSet<>(sp.getStringSet(key, new HashSet<>())); + } + + /** Best-effort attempts to migrate existing users to the new storage format. */ // TODO: Remove after sufficient time. Temporary migration storage for existing users. private static void migrateExistingUsersToNewStorage(Context context, String shortcutId, @@ -283,7 +297,11 @@ public class PeopleSpaceUtils { if (DEBUG) Log.d(TAG, "Migrate storage for " + entry.get().getUserName()); setStorageForTile(context, entry.get(), appWidgetId); } else { - Log.e(TAG, "Could not migrate user"); + Log.e(TAG, "Could not migrate user. Delete old storage"); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = sp.edit(); + editor.remove(String.valueOf(appWidgetId)); + editor.apply(); } } catch (Exception e) { Log.e(TAG, "Could not query conversations"); @@ -317,17 +335,10 @@ public class PeopleSpaceUtils { } /** Removes stored data when tile is deleted. */ - public static void removeStorageForTile(Context context, int widgetId) { - SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), - Context.MODE_PRIVATE); - String packageName = widgetSp.getString(PACKAGE_NAME, null); - String shortcutId = widgetSp.getString(SHORTCUT_ID, null); - int userId = widgetSp.getInt(USER_ID, -1); - + public static void removeStorageForTile(Context context, String key, int widgetId) { // Delete widgetId mapping to key. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); - String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); storedWidgetIds.remove(String.valueOf(widgetId)); editor.putStringSet(key, storedWidgetIds); @@ -335,6 +346,8 @@ public class PeopleSpaceUtils { editor.apply(); // Delete all data specifically mapped to widgetId. + SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), + Context.MODE_PRIVATE); SharedPreferences.Editor widgetEditor = widgetSp.edit(); widgetEditor.remove(PACKAGE_NAME); widgetEditor.remove(USER_ID); @@ -342,23 +355,8 @@ public class PeopleSpaceUtils { widgetEditor.apply(); } - /** - * Returns whether the data mapped to app widget specified by {@code appWidgetId} matches the - * requested update data. - */ - public static boolean isCorrectAppWidget(Context context, int appWidgetId, String shortcutId, - String packageName, int userId) { - SharedPreferences sp = context.getSharedPreferences(String.valueOf(appWidgetId), - Context.MODE_PRIVATE); - String storedPackage = sp.getString(PACKAGE_NAME, EMPTY_STRING); - int storedUserId = sp.getInt(USER_ID, INVALID_USER_ID); - String storedShortcutId = sp.getString(SHORTCUT_ID, EMPTY_STRING); - return storedPackage.equals(packageName) && storedShortcutId.equals(shortcutId) - && storedUserId == userId; - } - - static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(List<PeopleSpaceTile> tiles, - NotificationEntryManager notificationEntryManager) { + static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(Context context, + List<PeopleSpaceTile> tiles, NotificationEntryManager notificationEntryManager) { if (notificationEntryManager == null) { Log.w(TAG, "NotificationEntryManager is null"); return tiles; @@ -374,67 +372,54 @@ public class PeopleSpaceUtils { } return tiles .stream() - .map(entry -> augmentTileFromVisibleNotifications(entry, visibleNotifications)) + .map(entry -> augmentTileFromVisibleNotifications( + context, entry, visibleNotifications)) .collect(Collectors.toList()); } - static PeopleSpaceTile augmentTileFromVisibleNotifications(PeopleSpaceTile tile, - Map<String, NotificationEntry> visibleNotifications) { + static PeopleSpaceTile augmentTileFromVisibleNotifications(Context context, + PeopleSpaceTile tile, Map<String, NotificationEntry> visibleNotifications) { String shortcutId = tile.getId(); String packageName = tile.getPackageName(); - int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier(); + int userId = getUserId(tile); String key = getKey(shortcutId, packageName, userId); if (!visibleNotifications.containsKey(key)) { if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key); return tile; } if (DEBUG) Log.d(TAG, "Augmenting tile from visible notifications, key:" + key); - return augmentTileFromNotification(tile, visibleNotifications.get(key).getSbn()); + return augmentTileFromNotification(context, tile, visibleNotifications.get(key).getSbn()); } - /** - * If incoming notification changed tile, store the changes in the tile options. - */ - public static void updateWidgetWithNotificationChanged(IPeopleManager peopleManager, - Context context, - StatusBarNotification sbn, - NotificationAction notificationAction, AppWidgetManager appWidgetManager, - int appWidgetId) { - PeopleSpaceTile storedTile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, - appWidgetId); - if (storedTile == null) { - if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); - return; - } - if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { - if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); - storedTile = augmentTileFromNotification(storedTile, sbn); - } else { - if (DEBUG) { - Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); - } - storedTile = storedTile - .toBuilder() - .setNotificationKey(null) - .setNotificationContent(null) - .setNotificationDataUri(null) - .build(); + /** Augments {@code tile} with the notification content from {@code sbn}. */ + public static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile, + StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + if (notification == null) { + if (DEBUG) Log.d(TAG, "Notification is null"); + return tile; } - updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile); - } + boolean isMissedCall = Objects.equals(notification.category, CATEGORY_MISSED_CALL); + Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(notification); - static PeopleSpaceTile augmentTileFromNotification(PeopleSpaceTile tile, - StatusBarNotification sbn) { - Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn); - if (message == null) { - if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping."); + if (!isMissedCall && message == null) { + if (DEBUG) Log.d(TAG, "Notification has no content"); return tile; } + + // If it's a missed call notification and it doesn't include content, use fallback value, + // otherwise, use notification content. + boolean hasMessageText = message != null && !TextUtils.isEmpty(message.getText()); + CharSequence content = (isMissedCall && !hasMessageText) + ? context.getString(R.string.missed_call) : message.getText(); + Uri dataUri = message != null ? message.getDataUri() : null; + return tile .toBuilder() .setNotificationKey(sbn.getKey()) - .setNotificationContent(message.getText()) - .setNotificationDataUri(message.getDataUri()) + .setNotificationCategory(notification.category) + .setNotificationContent(content) + .setNotificationDataUri(dataUri) .build(); } @@ -462,6 +447,11 @@ public class PeopleSpaceUtils { * content, then birthdays, then the most recent status, and finally last interaction. */ private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile) { + if (Objects.equals(tile.getNotificationCategory(), CATEGORY_MISSED_CALL)) { + if (DEBUG) Log.d(TAG, "Create missed call view"); + return createMissedCallRemoteViews(context, tile); + } + if (tile.getNotificationKey() != null) { if (DEBUG) Log.d(TAG, "Create notification view"); return createNotificationRemoteViews(context, tile); @@ -617,7 +607,10 @@ public class PeopleSpaceUtils { activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); activityIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); - activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); + activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, + tile.getUserHandle()); + activityIntent.putExtra( + PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, tile.getNotificationKey()); views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity( context, appWidgetId, @@ -630,6 +623,16 @@ public class PeopleSpaceUtils { return views; } + private static RemoteViews createMissedCallRemoteViews(Context context, + PeopleSpaceTile tile) { + RemoteViews views = new RemoteViews( + context.getPackageName(), R.layout.people_space_small_avatar_tile); + views.setTextViewText(R.id.status, tile.getNotificationContent()); + views.setImageViewResource(R.id.status_defined_icon, R.drawable.ic_phone_missed); + views.setBoolean(R.id.content_background, "setClipToOutline", true); + return views; + } + private static RemoteViews createNotificationRemoteViews(Context context, PeopleSpaceTile tile) { RemoteViews views = new RemoteViews( @@ -715,8 +718,7 @@ public class PeopleSpaceUtils { /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */ @VisibleForTesting public static Notification.MessagingStyle.Message getLastMessagingStyleMessage( - StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); + Notification notification) { if (notification == null) { return null; } @@ -755,7 +757,6 @@ public class PeopleSpaceUtils { Log.i(TAG, "ConversationChannel is null"); return null; } - PeopleSpaceTile tile = new PeopleSpaceTile.Builder(channel, launcherApps).build(); if (!PeopleSpaceUtils.shouldKeepConversation(tile)) { Log.i(TAG, "PeopleSpaceTile is not valid"); @@ -957,7 +958,7 @@ public class PeopleSpaceUtils { } /** Update app widget options and the current view. */ - private static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager, + public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager, Context context, int appWidgetId, PeopleSpaceTile tile) { updateAppWidgetOptions(appWidgetManager, appWidgetId, tile); RemoteViews views = createRemoteViews(context, tile, appWidgetId); @@ -1030,17 +1031,21 @@ public class PeopleSpaceUtils { * <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li> * </ul> */ + @Nullable public static String getKey(String shortcutId, String packageName, int userId) { + if (!validKey(shortcutId, packageName, userId)) { + return null; + } return shortcutId + "/" + userId + "/" + packageName; } - /** Returns the userId associated with a {@link PeopleSpaceTile} */ - public static int getUserId(PeopleSpaceTile tile) { - return getUserHandle(tile).getIdentifier(); + /** Returns whether the key is valid. */ + public static boolean validKey(String shortcutId, String packageName, int userId) { + return !TextUtils.isEmpty(shortcutId) && !TextUtils.isEmpty(packageName) && userId >= 0; } - /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */ - public static UserHandle getUserHandle(PeopleSpaceTile tile) { - return UserHandle.getUserHandleForUid(tile.getUid()); + /** Returns the userId associated with a {@link PeopleSpaceTile} */ + public static int getUserId(PeopleSpaceTile tile) { + return tile.getUserHandle().getIdentifier(); } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java index 358f3113ef88..48f6184a96d9 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java @@ -17,21 +17,38 @@ package com.android.systemui.people.widget; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.os.Bundle; +import android.os.ServiceManager; import android.os.UserHandle; +import android.service.notification.NotificationStats; +import android.text.TextUtils; import android.util.Log; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.people.PeopleSpaceUtils; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import javax.inject.Inject; /** Proxy activity to launch ShortcutInfo's conversation. */ public class LaunchConversationActivity extends Activity { private static final String TAG = "PeopleSpaceLaunchConv"; private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + private NotificationEntryManager mNotificationEntryManager; + + @Inject + public LaunchConversationActivity(NotificationEntryManager notificationEntryManager) { + super(); + mNotificationEntryManager = notificationEntryManager; + } @Override public void onCreate(Bundle savedInstanceState) { @@ -41,7 +58,10 @@ public class LaunchConversationActivity extends Activity { Intent intent = getIntent(); String tileId = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID); String packageName = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME); - int uid = intent.getIntExtra(PeopleSpaceWidgetProvider.EXTRA_UID, 0); + UserHandle userHandle = intent.getParcelableExtra( + PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE); + String notificationKey = + intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY); if (tileId != null && !tileId.isEmpty()) { if (DEBUG) { @@ -52,13 +72,57 @@ public class LaunchConversationActivity extends Activity { LauncherApps launcherApps = getApplicationContext().getSystemService(LauncherApps.class); launcherApps.startShortcut( - packageName, tileId, null, null, UserHandle.getUserHandleForUid(uid)); + packageName, tileId, null, null, userHandle); + + IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + clearNotificationIfPresent( + statusBarService, notificationKey, packageName, userHandle); } catch (Exception e) { - Log.e(TAG, "Exception starting shortcut:" + e); + Log.e(TAG, "Exception:" + e); } } else { if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo."); } finish(); } + + void clearNotificationIfPresent(IStatusBarService statusBarService, + String notifKey, String packageName, UserHandle userHandle) { + if (TextUtils.isEmpty(notifKey)) { + if (DEBUG) Log.d(TAG, "Skipping clear notification: notification key is empty"); + return; + } + + try { + if (statusBarService == null || mNotificationEntryManager == null) { + if (DEBUG) { + Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey); + } + return; + } + + NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(notifKey); + if (entry == null || entry.getRanking() == null) { + if (DEBUG) { + Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking" + + " is null, key: " + notifKey); + } + return; + } + + int count = mNotificationEntryManager.getActiveNotificationsCount(); + int rank = entry.getRanking().getRank(); + NotificationVisibility notifVisibility = NotificationVisibility.obtain(notifKey, + rank, count, true); + + if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank); + statusBarService.onNotificationClear( + packageName, userHandle.getIdentifier(), notifKey, + NotificationStats.DISMISSAL_OTHER, + NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility); + } catch (Exception e) { + Log.e(TAG, "Exception cancelling notification:" + e); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java deleted file mode 100644 index b188acbf30f3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.people.widget; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.util.Log; - -import com.android.systemui.SystemUI; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.statusbar.FeatureFlags; - -import javax.inject.Inject; - -/** - * Enables People Space widgets. - */ -@SysUISingleton -public class PeopleSpaceWidgetEnabler extends SystemUI { - private static final String TAG = "PeopleSpaceWdgtEnabler"; - private Context mContext; - private FeatureFlags mFeatureFlags; - - @Inject - public PeopleSpaceWidgetEnabler(Context context, FeatureFlags featureFlags) { - super(context); - mContext = context; - mFeatureFlags = featureFlags; - } - - @Override - public void start() { - Log.d(TAG, "Starting service"); - try { - boolean showPeopleSpace = mFeatureFlags.isPeopleTileEnabled(); - mContext.getPackageManager().setComponentEnabledSetting( - new ComponentName(mContext, PeopleSpaceWidgetProvider.class), - showPeopleSpace - ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } catch (Exception e) { - Log.w(TAG, "Error enabling People Space widget:", e); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index bee9889eaa4e..22ee9e89d0a0 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -16,12 +16,28 @@ package com.android.systemui.people.widget; +import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID; +import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; +import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID; +import static com.android.systemui.people.PeopleSpaceUtils.USER_ID; +import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotification; +import static com.android.systemui.people.PeopleSpaceUtils.getPeopleSpaceTile; +import static com.android.systemui.people.PeopleSpaceUtils.getStoredWidgetIds; +import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView; + import android.app.NotificationChannel; +import android.app.Person; +import android.app.people.ConversationChannel; import android.app.people.IPeopleManager; +import android.app.people.PeopleManager; +import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.net.Uri; import android.os.ServiceManager; import android.os.UserHandle; import android.preference.PreferenceManager; @@ -29,16 +45,19 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; -import android.widget.RemoteViews; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.appwidget.IAppWidgetService; -import com.android.systemui.R; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import javax.inject.Inject; @@ -50,52 +69,49 @@ public class PeopleSpaceWidgetManager { private static final String TAG = "PeopleSpaceWidgetMgr"; private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; + private final Object mLock = new Object(); private final Context mContext; - private IAppWidgetService mAppWidgetService; + private LauncherApps mLauncherApps; private AppWidgetManager mAppWidgetManager; - private IPeopleManager mPeopleManager; + private IPeopleManager mIPeopleManager; + private SharedPreferences mSharedPrefs; + private PeopleManager mPeopleManager; + public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + @GuardedBy("mLock") + public static Map<String, PeopleSpaceWidgetProvider.TileConversationListener> mListeners = + new HashMap<>(); @Inject - public PeopleSpaceWidgetManager(Context context, IAppWidgetService appWidgetService) { + public PeopleSpaceWidgetManager(Context context) { if (DEBUG) Log.d(TAG, "constructor"); mContext = context; - mAppWidgetService = appWidgetService; mAppWidgetManager = AppWidgetManager.getInstance(context); - mPeopleManager = IPeopleManager.Stub.asInterface( + mIPeopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); - } - - /** - * Constructor used for testing. - */ - @VisibleForTesting - protected PeopleSpaceWidgetManager(Context context) { - if (DEBUG) Log.d(TAG, "constructor"); - mContext = context; - mAppWidgetService = IAppWidgetService.Stub.asInterface( - ServiceManager.getService(Context.APPWIDGET_SERVICE)); + mLauncherApps = context.getSystemService(LauncherApps.class); + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + mPeopleManager = mContext.getSystemService(PeopleManager.class); } /** * AppWidgetManager setter used for testing. */ @VisibleForTesting - protected void setAppWidgetManager(IAppWidgetService appWidgetService, - AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { - mAppWidgetService = appWidgetService; + protected void setAppWidgetManager( + AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager, + PeopleManager peopleManager, LauncherApps launcherApps) { mAppWidgetManager = appWidgetManager; + mIPeopleManager = iPeopleManager; mPeopleManager = peopleManager; + mLauncherApps = launcherApps; } /** * Updates People Space widgets. */ - public void updateWidgets() { + public void updateWidgets(int[] widgetIds) { try { if (DEBUG) Log.d(TAG, "updateWidgets called"); - int[] widgetIds = mAppWidgetService.getAppWidgetIds( - new ComponentName(mContext, PeopleSpaceWidgetProvider.class) - ); if (widgetIds.length == 0) { if (DEBUG) Log.d(TAG, "no widgets to update"); return; @@ -104,14 +120,11 @@ public class PeopleSpaceWidgetManager { if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets"); boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; - if (showSingleConversation) { - PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, - mAppWidgetManager, mPeopleManager); - } else { - mAppWidgetService - .notifyAppWidgetViewDataChanged(mContext.getOpPackageName(), widgetIds, - R.id.widget_list_view); + synchronized (mLock) { + PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, + mAppWidgetManager, mIPeopleManager); + } } } catch (Exception e) { Log.e(TAG, "Exception: " + e); @@ -122,11 +135,9 @@ public class PeopleSpaceWidgetManager { * Check if any existing People tiles match the incoming notification change, and store the * change in the tile if so. */ - public void updateWidgetWithNotificationChanged(StatusBarNotification sbn, + public void updateWidgetsWithNotificationChanged(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction) { - RemoteViews views = new RemoteViews( - mContext.getPackageName(), R.layout.people_space_small_avatar_tile); - if (DEBUG) Log.d(TAG, "updateWidgetWithNotificationChanged called"); + if (DEBUG) Log.d(TAG, "updateWidgetsWithNotificationChanged called"); boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (!showSingleConversation) { @@ -138,26 +149,22 @@ public class PeopleSpaceWidgetManager { if (DEBUG) Log.d(TAG, "Sbn shortcut id is null"); return; } - int[] widgetIds = mAppWidgetService.getAppWidgetIds( + int[] widgetIds = mAppWidgetManager.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class) ); if (widgetIds.length == 0) { Log.d(TAG, "No app widget ids returned"); return; } - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - int userId = UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(); - String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId); - Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); - if (storedWidgetIds.isEmpty()) { - Log.d(TAG, "No stored widget ids"); - return; - } - for (String widgetIdString : storedWidgetIds) { - int widgetId = Integer.parseInt(widgetIdString); - if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); - PeopleSpaceUtils.updateWidgetWithNotificationChanged(mPeopleManager, mContext, - sbn, notificationAction, mAppWidgetManager, widgetId); + synchronized (mLock) { + Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, sbnShortcutId, + sbn.getPackageName(), + UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier()); + for (String widgetIdString : storedWidgetIds) { + int widgetId = Integer.parseInt(widgetIdString); + if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); + updateStorageAndViewWithNotificationData(sbn, notificationAction, widgetId); + } } } catch (Exception e) { Log.e(TAG, "Exception: " + e); @@ -165,6 +172,91 @@ public class PeopleSpaceWidgetManager { } /** + * Update the tiles associated with the incoming conversation update. + */ + public void updateWidgetsWithConversationChanged(ConversationChannel conversation) { + ShortcutInfo info = conversation.getShortcutInfo(); + synchronized (mLock) { + Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, info.getId(), + info.getPackage(), + info.getUserId()); + for (String widgetIdString : storedWidgetIds) { + if (DEBUG) { + Log.d(TAG, + "Conversation update for widget " + widgetIdString + " , " + + info.getLabel()); + } + updateStorageAndViewWithConversationData(conversation, + Integer.valueOf(widgetIdString)); + } + } + } + + /** + * Update {@code appWidgetId} with the new data provided by {@code conversation}. + */ + private void updateStorageAndViewWithConversationData(ConversationChannel conversation, + int appWidgetId) { + PeopleSpaceTile storedTile = getPeopleSpaceTile(mIPeopleManager, mAppWidgetManager, + mContext, + appWidgetId); + if (storedTile == null) { + if (DEBUG) Log.d(TAG, "Could not find stored tile to add conversation to"); + return; + } + ShortcutInfo info = conversation.getShortcutInfo(); + Uri uri = null; + if (info.getPersons() != null && info.getPersons().length > 0) { + Person person = info.getPersons()[0]; + uri = person.getUri() == null ? null : Uri.parse(person.getUri()); + } + storedTile = storedTile.toBuilder() + .setUserName(info.getLabel()) + .setUserIcon( + PeopleSpaceTile.convertDrawableToIcon(mLauncherApps.getShortcutIconDrawable( + info, 0)) + ) + .setContactUri(uri) + .setStatuses(conversation.getStatuses()) + .setLastInteractionTimestamp(conversation.getLastEventTimestamp()) + .setIsImportantConversation(conversation.getParentNotificationChannel() != null + && conversation.getParentNotificationChannel().isImportantConversation()) + .build(); + updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId, storedTile); + } + + /** + * Update {@code appWidgetId} with the new data provided by {@code sbn}. + */ + private void updateStorageAndViewWithNotificationData( + StatusBarNotification sbn, + PeopleSpaceUtils.NotificationAction notificationAction, + int appWidgetId) { + PeopleSpaceTile storedTile = getPeopleSpaceTile(mIPeopleManager, mAppWidgetManager, + mContext, + appWidgetId); + if (storedTile == null) { + if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); + return; + } + if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { + if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); + storedTile = augmentTileFromNotification(mContext, storedTile, sbn); + } else { + if (DEBUG) { + Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); + } + storedTile = storedTile + .toBuilder() + .setNotificationKey(null) + .setNotificationContent(null) + .setNotificationDataUri(null) + .build(); + } + updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId, storedTile); + } + + /** * Attaches the manager to the pipeline, making it ready to receive events. Should only be * called once. */ @@ -177,8 +269,7 @@ public class PeopleSpaceWidgetManager { @Override public void onNotificationPosted( StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) { - if (DEBUG) Log.d(TAG, "onNotificationPosted"); - updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED); + updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED); } @Override @@ -186,8 +277,7 @@ public class PeopleSpaceWidgetManager { StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap ) { - if (DEBUG) Log.d(TAG, "onNotificationRemoved"); - updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); + updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override @@ -195,8 +285,7 @@ public class PeopleSpaceWidgetManager { StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap, int reason) { - if (DEBUG) Log.d(TAG, "onNotificationRemoved with reason " + reason); - updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); + updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override @@ -207,7 +296,6 @@ public class PeopleSpaceWidgetManager { @Override public void onNotificationsInitialized() { if (DEBUG) Log.d(TAG, "onNotificationsInitialized"); - updateWidgets(); } @Override @@ -216,11 +304,131 @@ public class PeopleSpaceWidgetManager { UserHandle user, NotificationChannel channel, int modificationType) { - if (DEBUG) Log.d(TAG, "onNotificationChannelModified"); if (channel.isConversation()) { - updateWidgets(); + updateWidgets(mAppWidgetManager.getAppWidgetIds( + new ComponentName(mContext, PeopleSpaceWidgetProvider.class) + )); } } }; -}
\ No newline at end of file + /** Adds {@code tile} mapped to {@code appWidgetId}. */ + public void addNewWidget(PeopleSpaceTile tile, int appWidgetId) { + mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED); + synchronized (mLock) { + if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getUserName()); + PeopleSpaceUtils.setStorageForTile(mContext, tile, appWidgetId); + } + try { + if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId()); + mLauncherApps.cacheShortcuts(tile.getPackageName(), + Collections.singletonList(tile.getId()), + tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); + } catch (Exception e) { + Log.w(TAG, "Exception caching shortcut:" + e); + } + PeopleSpaceWidgetProvider provider = new PeopleSpaceWidgetProvider(); + provider.onUpdate(mContext, mAppWidgetManager, new int[]{appWidgetId}); + } + + /** Registers a conversation listener for {@code appWidgetId} if not already registered. */ + public void registerConversationListenerIfNeeded(int widgetId, + PeopleSpaceWidgetProvider.TileConversationListener newListener) { + // Retrieve storage needed for registration. + String packageName; + String shortcutId; + int userId; + String key; + synchronized (mLock) { + SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId), + Context.MODE_PRIVATE); + packageName = widgetSp.getString(PACKAGE_NAME, null); + shortcutId = widgetSp.getString(SHORTCUT_ID, null); + userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); + key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); + if (key == null) { + if (DEBUG) Log.e(TAG, "Could not register " + widgetId); + return; + } + } + synchronized (mListeners) { + if (mListeners.containsKey(key)) { + if (DEBUG) Log.d(TAG, "Already registered listener"); + return; + } + if (DEBUG) Log.d(TAG, "Register listener for " + widgetId + " with " + key); + mListeners.put(key, newListener); + } + mPeopleManager.registerConversationListener(packageName, + userId, + shortcutId, newListener, + mContext.getMainExecutor()); + } + + /** Deletes all storage, listeners, and caching for {@code appWidgetIds}. */ + public void deleteWidgets(int[] appWidgetIds) { + for (int widgetId : appWidgetIds) { + if (DEBUG) Log.d(TAG, "Widget removed: " + widgetId); + mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); + // Retrieve storage needed for widget deletion. + String packageName; + String shortcutId; + int userId; + String key; + Set<String> storedWidgetIdsForKey; + synchronized (mLock) { + SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId), + Context.MODE_PRIVATE); + packageName = widgetSp.getString(PACKAGE_NAME, null); + shortcutId = widgetSp.getString(SHORTCUT_ID, null); + userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); + key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); + if (key == null) { + if (DEBUG) Log.e(TAG, "Could not delete " + widgetId); + return; + } + storedWidgetIdsForKey = new HashSet<>( + mSharedPrefs.getStringSet(key, new HashSet<>())); + } + synchronized (mLock) { + PeopleSpaceUtils.removeStorageForTile(mContext, key, widgetId); + } + // If another tile with the conversation is still stored, we need to keep the listener. + if (DEBUG) Log.d(TAG, "Stored widget IDs: " + storedWidgetIdsForKey.toString()); + if (storedWidgetIdsForKey.contains(String.valueOf(widgetId)) + && storedWidgetIdsForKey.size() == 1) { + if (DEBUG) Log.d(TAG, "Remove caching and listener"); + unregisterConversationListener(key, widgetId); + uncacheConversationShortcut(shortcutId, packageName, userId); + } + } + } + + /** Unregisters the conversation listener for {@code appWidgetId}. */ + private void unregisterConversationListener(String key, int appWidgetId) { + PeopleSpaceWidgetProvider.TileConversationListener registeredListener; + synchronized (mListeners) { + registeredListener = mListeners.get(key); + if (registeredListener == null) { + if (DEBUG) Log.d(TAG, "Cannot find listener to unregister"); + return; + } + if (DEBUG) Log.d(TAG, "Unregister listener for " + appWidgetId + " with " + key); + mListeners.remove(key); + } + mPeopleManager.unregisterConversationListener(registeredListener); + } + + /** Uncaches the conversation shortcut. */ + private void uncacheConversationShortcut(String shortcutId, String packageName, int userId) { + try { + if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId); + mLauncherApps.uncacheShortcuts(packageName, + Collections.singletonList(shortcutId), + UserHandle.of(userId), + LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); + } catch (Exception e) { + Log.d(TAG, "Exception uncaching shortcut:" + e); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java index 3d1055fdece2..cccf7aa13028 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java @@ -16,31 +16,17 @@ package com.android.systemui.people.widget; -import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; -import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID; -import static com.android.systemui.people.PeopleSpaceUtils.USER_ID; - -import android.app.PendingIntent; -import android.app.people.IPeopleManager; +import android.annotation.NonNull; +import android.app.people.ConversationChannel; +import android.app.people.PeopleManager; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.LauncherApps; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; -import android.widget.RemoteViews; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.UiEventLoggerImpl; -import com.android.systemui.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.people.PeopleSpaceUtils; -import java.util.Collections; - /** People Space Widget Provider class. */ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { private static final String TAG = "PeopleSpaceWidgetPvd"; @@ -48,9 +34,29 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { public static final String EXTRA_TILE_ID = "extra_tile_id"; public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; - public static final String EXTRA_UID = "extra_uid"; + public static final String EXTRA_USER_HANDLE = "extra_user_handle"; + public static final String EXTRA_NOTIFICATION_KEY = "extra_notification_key"; + + public PeopleSpaceWidgetManager peopleSpaceWidgetManager; - public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + /** Listener for the shortcut data changes. */ + public class TileConversationListener implements PeopleManager.ConversationListener { + + @Override + public void onConversationUpdate(@NonNull ConversationChannel conversation) { + if (DEBUG) { + Log.d(TAG, + "Received updated conversation: " + + conversation.getShortcutInfo().getLabel()); + } + if (peopleSpaceWidgetManager == null) { + // This shouldn't happen since onUpdate is called at reboot. + Log.e(TAG, "Skipping conversation update: WidgetManager uninitialized"); + return; + } + peopleSpaceWidgetManager.updateWidgetsWithConversationChanged(conversation); + } + } /** Called when widget updates. */ @Override @@ -58,70 +64,32 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { super.onUpdate(context, appWidgetManager, appWidgetIds); if (DEBUG) Log.d(TAG, "onUpdate called"); - boolean showSingleConversation = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; - if (showSingleConversation) { - PeopleSpaceUtils.updateSingleConversationWidgets(context, appWidgetIds, - appWidgetManager, IPeopleManager.Stub.asInterface( - ServiceManager.getService(Context.PEOPLE_SERVICE))); - return; - } - // Perform this loop procedure for each App Widget that belongs to this provider + ensurePeopleSpaceWidgetManagerInitialized(context); + peopleSpaceWidgetManager.updateWidgets(appWidgetIds); for (int appWidgetId : appWidgetIds) { - RemoteViews views = - new RemoteViews(context.getPackageName(), R.layout.people_space_widget); - - Intent intent = new Intent(context, PeopleSpaceWidgetService.class); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - views.setRemoteAdapter(R.id.widget_list_view, intent); - - Intent activityIntent = new Intent(context, LaunchConversationActivity.class); - activityIntent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_NO_HISTORY - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - PendingIntent pendingIntent = PendingIntent.getActivity( - context, - appWidgetId, - activityIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent); + PeopleSpaceWidgetProvider.TileConversationListener + newListener = new PeopleSpaceWidgetProvider.TileConversationListener(); + peopleSpaceWidgetManager.registerConversationListenerIfNeeded(appWidgetId, + newListener); + } + return; + } - // Tell the AppWidgetManager to perform an update on the current app widget - appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list_view); - appWidgetManager.updateAppWidget(appWidgetId, views); + private void ensurePeopleSpaceWidgetManagerInitialized(Context context) { + if (peopleSpaceWidgetManager == null) { + peopleSpaceWidgetManager = new PeopleSpaceWidgetManager(context); } } @Override public void onDeleted(Context context, int[] appWidgetIds) { super.onDeleted(context, appWidgetIds); - LauncherApps launcherApps = context.getSystemService(LauncherApps.class); - - for (int widgetId : appWidgetIds) { - if (DEBUG) Log.d(TAG, "Widget removed"); - mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); - if (launcherApps != null) { - SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), - Context.MODE_PRIVATE); - String packageName = widgetSp.getString(PACKAGE_NAME, null); - String shortcutId = widgetSp.getString(SHORTCUT_ID, null); - int userId = widgetSp.getInt(USER_ID, -1); + ensurePeopleSpaceWidgetManagerInitialized(context); + peopleSpaceWidgetManager.deleteWidgets(appWidgetIds); + } - if (packageName != null && shortcutId != null && userId != -1) { - try { - if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId); - launcherApps.uncacheShortcuts(packageName, - Collections.singletonList(shortcutId), - UserHandle.of(userId), - LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); - } catch (Exception e) { - Log.d(TAG, "Exception uncaching shortcut:" + e); - } - } - } - PeopleSpaceUtils.removeStorageForTile(context, widgetId); - } + @VisibleForTesting + public void setPeopleSpaceWidgetManager(PeopleSpaceWidgetManager manager) { + peopleSpaceWidgetManager = manager; } } diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java index 80794cb64883..050352292b38 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java @@ -123,7 +123,8 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); fillInIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); - fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); + fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, + tile.getUserHandle()); personView.setOnClickFillInIntent(R.id.item, fillInIntent); } catch (Exception e) { Log.e(TAG, "Couldn't retrieve shortcut information", e); diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt index 680a617e4d26..8ec9b682ffdc 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt @@ -47,7 +47,7 @@ class PrivacyDialog( context: Context, private val list: List<PrivacyElement>, activityStarter: (String, Int) -> Unit -) : SystemUIDialog(context, R.style.ScreenRecord) { +) : SystemUIDialog(context, R.style.PrivacyDialog) { private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>() private val dismissed = AtomicBoolean(false) @@ -64,6 +64,7 @@ class PrivacyDialog( super.onCreate(savedInstanceState) window?.apply { attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() + attributes.receiveInsetsIgnoringZOrder = true setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT) setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java index c9d1b71bca77..0fa7b59d0e54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java +++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.systemui.statusbar.tv.micdisclosure; +package com.android.systemui.privacy.television; -import static android.provider.DeviceConfig.NAMESPACE_PRIVACY; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.animation.Animator; @@ -25,57 +24,56 @@ import android.animation.ObjectAnimator; import android.annotation.IntDef; import android.annotation.UiThread; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; import android.graphics.PixelFormat; -import android.provider.DeviceConfig; -import android.text.TextUtils; -import android.util.ArraySet; +import android.graphics.drawable.Drawable; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; import com.android.systemui.R; -import com.android.systemui.statusbar.tv.TvStatusBar; +import com.android.systemui.SystemUI; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.privacy.PrivacyChipBuilder; +import com.android.systemui.privacy.PrivacyItem; +import com.android.systemui.privacy.PrivacyItemController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Set; + +import javax.inject.Inject; /** - * A component of {@link TvStatusBar} responsible for notifying the user whenever an application is - * recording audio. - * - * @see TvStatusBar + * A SystemUI component responsible for notifying the user whenever an application is + * recording audio, accessing the camera or accessing the location. */ -public class AudioRecordingDisclosureBar implements - AudioActivityObserver.OnAudioActivityStateChangeListener { - private static final String TAG = "AudioRecordingDisclosure"; +@SysUISingleton +public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemController.Callback { + private static final String TAG = "TvOngoingPrivacyChip"; static final boolean DEBUG = false; - // This title is used to test the microphone disclosure indicator in - // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest + // This title is used in CameraMicIndicatorsPermissionTest and + // RecognitionServiceMicIndicatorTest. private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator"; - private static final String ENABLED_FLAG = "mic_disclosure_enabled"; - private static final String EXEMPT_PACKAGES_LIST = "mic_disclosure_exempt_packages"; - private static final String FORCED_PACKAGES_LIST = "mic_disclosure_forced_packages"; - @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"STATE_"}, value = { - STATE_STOPPED, STATE_NOT_SHOWN, STATE_APPEARING, STATE_SHOWN, STATE_DISAPPEARING }) - public @interface State {} + public @interface State { + } - private static final int STATE_STOPPED = -1; private static final int STATE_NOT_SHOWN = 0; private static final int STATE_APPEARING = 1; private static final int STATE_SHOWN = 2; @@ -84,106 +82,89 @@ public class AudioRecordingDisclosureBar implements private static final int ANIMATION_DURATION_MS = 200; private final Context mContext; - private boolean mIsEnabled; + private final PrivacyItemController mPrivacyItemController; private View mIndicatorView; private boolean mViewAndWindowAdded; private ObjectAnimator mAnimator; - @State private int mState = STATE_STOPPED; + private boolean mAllIndicatorsFlagEnabled; + private boolean mMicCameraIndicatorFlagEnabled; + private boolean mLocationIndicatorEnabled; + private List<PrivacyItem> mPrivacyItems; - /** - * Array of the observers that monitor different aspects of the system, such as AppOps and - * microphone foreground services - */ - private AudioActivityObserver[] mAudioActivityObservers; - /** - * Set of applications for which we make an exception and do not show the indicator. This gets - * populated once - in {@link #AudioRecordingDisclosureBar(Context)}. - */ - private final Set<String> mExemptPackages = new ArraySet<>(); + private LinearLayout mIconsContainer; + private final int mIconSize; + private final int mIconMarginStart; - public AudioRecordingDisclosureBar(Context context) { + @State + private int mState = STATE_NOT_SHOWN; + + @Inject + public TvOngoingPrivacyChip(Context context, PrivacyItemController privacyItemController) { + super(context); + Log.d(TAG, "Privacy chip running without id"); mContext = context; + mPrivacyItemController = privacyItemController; - // Load configs - reloadExemptPackages(); + Resources res = mContext.getResources(); + mIconMarginStart = Math.round(res.getDimension(R.dimen.privacy_chip_icon_margin)); + mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size); - mIsEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY, ENABLED_FLAG, true); - // Start if enabled - if (mIsEnabled) { - start(); - } + mAllIndicatorsFlagEnabled = privacyItemController.getAllIndicatorsAvailable(); + mMicCameraIndicatorFlagEnabled = privacyItemController.getMicCameraAvailable(); + mLocationIndicatorEnabled = privacyItemController.getLocationAvailable(); - // Set up a config change listener - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_PRIVACY, mContext.getMainExecutor(), - mConfigChangeListener); + if (DEBUG) { + Log.d(TAG, "allIndicators: " + mAllIndicatorsFlagEnabled); + Log.d(TAG, "micCameraIndicators: " + mMicCameraIndicatorFlagEnabled); + Log.d(TAG, "locationIndicators: " + mLocationIndicatorEnabled); + } } - private void reloadExemptPackages() { - mExemptPackages.clear(); - mExemptPackages.addAll(Arrays.asList(mContext.getResources().getStringArray( - R.array.audio_recording_disclosure_exempt_apps))); - mExemptPackages.addAll( - splitByComma( - DeviceConfig.getString(NAMESPACE_PRIVACY, EXEMPT_PACKAGES_LIST, null))); - mExemptPackages.removeAll( - splitByComma( - DeviceConfig.getString(NAMESPACE_PRIVACY, FORCED_PACKAGES_LIST, null))); + @Override + public void start() { + mPrivacyItemController.addCallback(this); } - @UiThread - private void start() { - if (mState != STATE_STOPPED) { - return; - } - mState = STATE_NOT_SHOWN; - - if (mAudioActivityObservers == null) { - mAudioActivityObservers = new AudioActivityObserver[]{ - new RecordAudioAppOpObserver(mContext, this), - new MicrophoneForegroundServicesObserver(mContext, this), - }; - } - - for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) { - mAudioActivityObservers[i].start(); - } + @Override + public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) { + if (DEBUG) Log.d(TAG, "PrivacyItemsChanged"); + mPrivacyItems = privacyItems; + updateUI(); } - @UiThread - private void stop() { - if (mState == STATE_STOPPED) { - return; - } - mState = STATE_STOPPED; - - for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) { - mAudioActivityObservers[i].stop(); - } - - // Remove the view if shown. - if (mState != STATE_NOT_SHOWN) { - removeIndicatorView(); - } + @Override + public void onFlagAllChanged(boolean flag) { + if (DEBUG) Log.d(TAG, "all indicators enabled: " + flag); + mAllIndicatorsFlagEnabled = flag; } - @UiThread @Override - public void onAudioActivityStateChange(boolean active, String packageName) { - if (DEBUG) { - Log.d(TAG, - "onAudioActivityStateChange, packageName=" + packageName + ", active=" - + active); - } + public void onFlagMicCameraChanged(boolean flag) { + if (DEBUG) Log.d(TAG, "mic/camera indicators enabled: " + flag); + mMicCameraIndicatorFlagEnabled = flag; + } - if (mExemptPackages.contains(packageName)) { - if (DEBUG) Log.d(TAG, " - exempt package: ignoring"); - return; - } + @Override + public void onFlagLocationChanged(boolean flag) { + if (DEBUG) Log.d(TAG, "location indicators enabled: " + flag); + mLocationIndicatorEnabled = flag; + } - if (active) { - showIfNeeded(); + private void updateUI() { + if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items"); + + if ((mMicCameraIndicatorFlagEnabled || mAllIndicatorsFlagEnabled + || mLocationIndicatorEnabled) && !mPrivacyItems.isEmpty()) { + if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) { + showIndicator(); + } else { + if (DEBUG) Log.d(TAG, "only updating icons"); + PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems); + setIcons(builder.generateIcons(), mIconsContainer); + mIconsContainer.requestLayout(); + } } else { hideIndicatorIfNeeded(); } @@ -191,12 +172,7 @@ public class AudioRecordingDisclosureBar implements @UiThread private void hideIndicatorIfNeeded() { - // If STOPPED, NOT_SHOWN or DISAPPEARING - nothing else for us to do here. - if (mState != STATE_SHOWN && mState != STATE_APPEARING) return; - - if (hasActiveRecorders()) { - return; - } + if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) return; if (mViewAndWindowAdded) { mState = STATE_DISAPPEARING; @@ -210,23 +186,12 @@ public class AudioRecordingDisclosureBar implements } @UiThread - private void showIfNeeded() { - // If STOPPED, SHOWN or APPEARING - nothing else for us to do here. - if (mState != STATE_NOT_SHOWN && mState != STATE_DISAPPEARING) return; - - if (DEBUG) Log.d(TAG, "Showing indicator"); - - final int prevState = mState; + private void showIndicator() { mState = STATE_APPEARING; - if (prevState == STATE_DISAPPEARING) { - animateAppearance(); - return; - } - // Inflate the indicator view mIndicatorView = LayoutInflater.from(mContext).inflate( - R.layout.tv_audio_recording_indicator, null); + R.layout.tv_ongoing_privacy_chip, null); // 1. Set alpha to 0. // 2. Wait until the window is shown and the view is laid out. @@ -239,7 +204,7 @@ public class AudioRecordingDisclosureBar implements @Override public void onGlobalLayout() { // State could have changed to NOT_SHOWN (if all the recorders are - // already gone) to STOPPED (if the indicator was disabled) + // already gone) if (mState != STATE_APPEARING) return; mViewAndWindowAdded = true; @@ -251,22 +216,41 @@ public class AudioRecordingDisclosureBar implements } }); - final boolean isLtr = mContext.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; + mIconsContainer = mIndicatorView.findViewById(R.id.icons_container); + PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems); + setIcons(builder.generateIcons(), mIconsContainer); + final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( WRAP_CONTENT, WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); - layoutParams.gravity = Gravity.TOP | (isLtr ? Gravity.RIGHT : Gravity.LEFT); + layoutParams.gravity = Gravity.TOP | Gravity.END; layoutParams.setTitle(LAYOUT_PARAMS_TITLE); layoutParams.packageName = mContext.getPackageName(); - final WindowManager windowManager = (WindowManager) mContext.getSystemService( - Context.WINDOW_SERVICE); + final WindowManager windowManager = mContext.getSystemService(WindowManager.class); windowManager.addView(mIndicatorView, layoutParams); + } + private void setIcons(List<Drawable> icons, ViewGroup iconsContainer) { + iconsContainer.removeAllViews(); + for (int i = 0; i < icons.size(); i++) { + Drawable icon = icons.get(i); + icon.mutate().setTint(Color.WHITE); + ImageView imageView = new ImageView(mContext); + imageView.setImageDrawable(icon); + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + mIconsContainer.addView(imageView, mIconSize, mIconSize); + if (i != 0) { + ViewGroup.MarginLayoutParams layoutParams = + (ViewGroup.MarginLayoutParams) imageView.getLayoutParams(); + layoutParams.setMarginStart(mIconMarginStart); + imageView.setLayoutParams(layoutParams); + } + } + } private void animateAppearance() { animateAlphaTo(1f); @@ -333,22 +317,13 @@ public class AudioRecordingDisclosureBar implements } } - private boolean hasActiveRecorders() { - for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) { - for (String activePackage : mAudioActivityObservers[index].getActivePackages()) { - if (mExemptPackages.contains(activePackage)) continue; - return true; - } - } - return false; - } - private void removeIndicatorView() { if (DEBUG) Log.d(TAG, "removeIndicatorView"); - final WindowManager windowManager = (WindowManager) mContext.getSystemService( - Context.WINDOW_SERVICE); - windowManager.removeView(mIndicatorView); + final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + if (windowManager != null) { + windowManager.removeView(mIndicatorView); + } mIndicatorView = null; mAnimator = null; @@ -356,26 +331,4 @@ public class AudioRecordingDisclosureBar implements mViewAndWindowAdded = false; } - private static List<String> splitByComma(String string) { - return TextUtils.isEmpty(string) ? Collections.emptyList() : Arrays.asList( - string.split(",")); - } - - private final DeviceConfig.OnPropertiesChangedListener mConfigChangeListener = - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - reloadExemptPackages(); - - // Check if was enabled/disabled - if (mIsEnabled != properties.getBoolean(ENABLED_FLAG, true)) { - mIsEnabled = !mIsEnabled; - if (mIsEnabled) { - start(); - } else { - stop(); - } - } - } - }; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index eaf212362320..fcb56a4ec75d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -71,7 +71,6 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private int mMinRows = 1; private int mMaxColumns = TileLayout.NO_MAX_COLUMNS; - private boolean mShowLabels = true; private final boolean mSideLabels; public PagedTileLayout(Context context, AttributeSet attrs) { @@ -91,16 +90,6 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } private int mLastMaxHeight = -1; - @Override - public void setShowLabels(boolean show) { - mShowLabels = show; - for (TileLayout p : mPages) { - p.setShowLabels(show); - } - mDistributeTiles = true; - requestLayout(); - } - public void saveInstanceState(Bundle outState) { outState.putInt(CURRENT_PAGE, getCurrentItem()); } @@ -239,7 +228,6 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { : R.layout.qs_paged_page, this, false); page.setMinRows(mMinRows); page.setMaxColumns(mMaxColumns); - page.setShowLabels(mShowLabels); return page; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index c8edaec98ee4..fe76668ab68b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -29,7 +29,6 @@ import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.qs.tileimpl.QSTileBaseView; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -56,7 +55,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final ArrayList<View> mAllViews = new ArrayList<>(); /** * List of {@link View}s representing Quick Settings that are being animated from the quick QS - * position to the normal QS panel. + * position to the normal QS panel. These views will only show once the animation is complete, + * to prevent overlapping of semi transparent views */ private final ArrayList<View> mQuickQsViews = new ArrayList<>(); private final QuickQSPanel mQuickQsPanel; @@ -233,7 +233,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // Quick tiles. QSTileView quickTileView = mQuickQSPanelController.getTileView(tile); if (quickTileView == null) continue; - View qqsBgCircle = ((QSTileBaseView) quickTileView).getBgCircle(); getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view); getRelativePosition(loc2, tileIcon, view); @@ -255,11 +254,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); - if (mFeatureFlags.isQSLabelsEnabled()) { - firstPageBuilder.addFloat(qqsBgCircle, "alpha", 1, 1, 0); - mAllViews.add(qqsBgCircle); - } - } else { // These tiles disappear when expanding firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0); translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); @@ -271,7 +265,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationX); } - mQuickQsViews.add(tileView.getIconWithBackground()); + if (mFeatureFlags.isQSLabelsEnabled()) { + mQuickQsViews.add(tileView); + } else { + mQuickQsViews.add(tileView.getIconWithBackground()); + } mAllViews.add(tileView.getIcon()); mAllViews.add(quickTileView); } else if (mFullRows && isIconInAnimatedRow(count)) { @@ -362,7 +360,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if(view == parent || view == null) return; // Ignore tile pages as they can have some offset we don't want to take into account in // RTL. - if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) { + if (!isAPage(view)) { loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } @@ -374,6 +372,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha getRelativePositionInt(loc1, (View) view.getParent(), parent); } + // Returns true if the view is a possible page in PagedTileLayout + private boolean isAPage(View view) { + if (view instanceof PagedTileLayout.TilePage) { + return true; + } else if (view instanceof SideLabelTileLayout) { + return !(view instanceof QuickQSPanel.QQSSideLabelTileLayout); + } + return false; + } + public void setPosition(float position) { if (mNeedsAnimatorUpdate) { updateAnimators(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 7e20be6826dc..3d8784b29e4c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -94,7 +94,7 @@ public class QSFooterView extends FrameLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mEdit = findViewById(android.R.id.edit); + mEdit = requireViewById(android.R.id.edit); mPageIndicator = findViewById(R.id.footer_page_indicator); @@ -104,14 +104,15 @@ public class QSFooterView extends FrameLayout { mMultiUserSwitch = findViewById(R.id.multi_user_switch); mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); - mActionsContainer = findViewById(R.id.qs_footer_actions_container); + mActionsContainer = requireViewById(R.id.qs_footer_actions_container); mEditContainer = findViewById(R.id.qs_footer_actions_edit_container); mBuildText = findViewById(R.id.build); // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view - ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); - + if (mSettingsButton.getBackground() instanceof RippleDrawable) { + ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); + } updateResources(); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); @@ -143,7 +144,7 @@ public class QSFooterView extends FrameLayout { int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space); mSettingsCogAnimator = new Builder() - .addFloat(mSettingsContainer, "translationX", + .addFloat(mSettingsButton, "translationX", isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0) .addFloat(mSettingsButton, "rotation", -120, 0) .build(); @@ -173,12 +174,15 @@ public class QSFooterView extends FrameLayout { @Nullable private TouchAnimator createFooterAnimator() { - return new TouchAnimator.Builder() + TouchAnimator.Builder builder = new TouchAnimator.Builder() .addFloat(mActionsContainer, "alpha", 0, 1) - .addFloat(mEditContainer, "alpha", 0, 1) .addFloat(mPageIndicator, "alpha", 0, 1) - .setStartDelay(0.9f) - .build(); + .addFloat(mBuildText, "alpha", 0, 1) + .setStartDelay(0.9f); + if (mEditContainer != null) { + builder.addFloat(mEditContainer, "alpha", 0, 1); + } + return builder.build(); } /** */ @@ -273,8 +277,10 @@ public class QSFooterView extends FrameLayout { mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( isTunerEnabled ? View.VISIBLE : View.INVISIBLE); final boolean isDemo = UserManager.isDeviceInDemoMode(mContext); - mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE); - mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); + mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.GONE); + if (mEditContainer != null) { + mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); + } mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 8110fda1330c..f1f4e16206a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.qs; +import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED; + import android.content.ClipData; import android.content.ClipboardManager; import android.content.Intent; @@ -41,6 +43,7 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.util.ViewController; import javax.inject.Inject; +import javax.inject.Named; /** * Controller for {@link QSFooterView}. @@ -63,6 +66,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final View mEdit; private final MultiUserSwitch mMultiUserSwitch; private final PageIndicator mPageIndicator; + private final View mPowerMenuLite; + private final boolean mShowPMLiteButton; private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = new UserInfoController.OnUserInfoChangedListener() { @@ -95,7 +100,6 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mActivityStarter.postQSRunnableDismissingKeyguard(() -> { if (isTunerEnabled()) { mTunerService.showResetRequest( - mUserTracker.getUserHandle(), () -> { // Relaunch settings so that the tuner disappears. startSettingsActivity(); @@ -103,7 +107,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme } else { Toast.makeText(getContext(), R.string.tuner_toast, Toast.LENGTH_LONG).show(); - mTunerService.setTunerEnabled(mUserTracker.getUserHandle(), true); + mTunerService.setTunerEnabled(true); } startSettingsActivity(); @@ -124,7 +128,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, QSPanelController qsPanelController, QSDetailDisplayer qsDetailDisplayer, QuickQSPanelController quickQSPanelController, - TunerService tunerService, MetricsLogger metricsLogger) { + TunerService tunerService, MetricsLogger metricsLogger, + @Named(PM_LITE_ENABLED) boolean showPMLiteButton) { super(view); mUserManager = userManager; mUserInfoController = userInfoController; @@ -142,10 +147,15 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mEdit = mView.findViewById(android.R.id.edit); mMultiUserSwitch = mView.findViewById(R.id.multi_user_switch); mPageIndicator = mView.findViewById(R.id.footer_page_indicator); + mPowerMenuLite = mView.findViewById(R.id.pm_lite); + mShowPMLiteButton = showPMLiteButton; } @Override protected void onViewAttached() { + if (!mShowPMLiteButton) { + mPowerMenuLite.setVisibility(View.GONE); + } mView.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> mView.updateAnimator( @@ -238,6 +248,6 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme } private boolean isTunerEnabled() { - return mTunerService.isTunerEnabled(mUserTracker.getUserHandle()); + return mTunerService.isTunerEnabled(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 91ae571d1cfb..ff9b9120c6e1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -32,6 +32,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewStub; import android.widget.LinearLayout; import com.android.internal.logging.UiEventLogger; @@ -112,7 +113,7 @@ public class QSPanel extends LinearLayout implements Tunable { private int mMediaTotalBottomMargin; private int mFooterMarginStartHorizontal; private Consumer<Boolean> mMediaVisibilityChangedListener; - private boolean mSideLabels; + protected boolean mSideLabels; public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -127,8 +128,21 @@ public class QSPanel extends LinearLayout implements Tunable { } + protected void inflateQSFooter(boolean newFooter) { + ViewStub stub = findViewById(R.id.qs_footer_stub); + if (stub != null) { + stub.setLayoutResource( + newFooter ? R.layout.qs_footer_impl_two_lines : R.layout.qs_footer_impl); + stub.inflate(); + mFooter = findViewById(R.id.qs_footer); + } + } + void initialize(boolean sideLabels) { mSideLabels = sideLabels; + + inflateQSFooter(sideLabels); + mRegularTileLayout = createRegularTileLayout(); mTileLayout = mRegularTileLayout; @@ -201,16 +215,20 @@ public class QSPanel extends LinearLayout implements Tunable { mFooterPageIndicator.setNumPages(((PagedTileLayout) mTileLayout).getNumPages()); } - // Allow the UI to be as big as it want's to, we're in a scroll view - int newHeight = 10000; - int availableHeight = MeasureSpec.getSize(heightMeasureSpec); - int excessHeight = newHeight - availableHeight; - // Measure with EXACTLY. That way, The content will only use excess height and will - // be measured last, after other views and padding is accounted for. This only - // works because our Layouts in here remeasure themselves with the exact content - // height. - heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); - ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + // In landscape, mTileLayout's parent is not the panel but a view that contains the + // tile layout and the media controls. + if (((View) mTileLayout).getParent() == this) { + // Allow the UI to be as big as it want's to, we're in a scroll view + int newHeight = 10000; + int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + int excessHeight = newHeight - availableHeight; + // Measure with EXACTLY. That way, The content will only use excess height and will + // be measured last, after other views and padding is accounted for. This only + // works because our Layouts in here remeasure themselves with the exact content + // height. + heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); + ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -340,7 +358,6 @@ public class QSPanel extends LinearLayout implements Tunable { @Override protected void onFinishInflate() { super.onFinishInflate(); - mFooter = findViewById(R.id.qs_footer); mDivider = findViewById(R.id.divider); } @@ -758,7 +775,7 @@ public class QSPanel extends LinearLayout implements Tunable { // Let's use 3 columns to match the current layout int columns; if (mSideLabels) { - columns = horizontal ? 1 : 2; + columns = horizontal ? 2 : 4; } else { columns = horizontal ? 3 : TileLayout.NO_MAX_COLUMNS; } @@ -843,8 +860,6 @@ public class QSPanel extends LinearLayout implements Tunable { default void setExpansion(float expansion) {} int getNumVisibleTiles(); - - default void setShowLabels(boolean show) {} } interface OnConfigurationChangedListener { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index fcb35e2040ea..eda1abb0997e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -18,7 +18,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import android.annotation.NonNull; @@ -66,7 +65,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private BrightnessMirrorController mBrightnessMirrorController; private boolean mGridContentVisible = true; - private boolean mQsLabelsFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -93,7 +91,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSlider.Factory brightnessSliderFactory, - @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, FeatureFlags featureFlags) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); @@ -108,9 +105,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mView.setBrightnessView(mBrightnessSlider.getRootView()); mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider); - - mQsLabelsFlag = qsLabelsFlag; - mSideLabels = qsLabelsFlag; } @Override @@ -130,7 +124,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { updateMediaDisappearParameters(); mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS); - mTunerService.addTunable(mTunable, QS_REMOVE_LABELS); mView.updateResources(); if (mView.isListening()) { refreshAllTiles(); @@ -144,13 +137,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } @Override - boolean switchTileLayout(boolean force) { - boolean result = super.switchTileLayout(force); - getTileLayout().setShowLabels(mShowLabels); - return result; - } - - @Override protected QSTileRevealController createTileRevealController() { return mQsTileRevealControllerFactory.create( this, (PagedTileLayout) mView.createRegularTileLayout()); @@ -158,7 +144,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override protected void onViewDetached() { - mTunerService.removeTunable(mTunable); mTunerService.removeTunable(mView); mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener); if (mBrightnessMirrorController != null) { @@ -324,22 +309,5 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { public boolean isExpanded() { return mView.isExpanded(); } - - private TunerService.Tunable mTunable = new TunerService.Tunable() { - @Override - public void onTuningChanged(String key, String newValue) { - if (QS_REMOVE_LABELS.equals(key)) { - if (!mQsLabelsFlag) return; - boolean newShowLabels = newValue == null || "0".equals(newValue); - if (mShowLabels == newShowLabels) return; - mShowLabels = newShowLabels; - for (TileRecord t : mRecords) { - t.tileView.setShowLabels(mShowLabels); - } - getTileLayout().setShowLabels(mShowLabels); - mView.requestLayout(); - } - } - }; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 9426e7122c1c..8ab17432524d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -75,7 +75,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final QSHost.Callback mQSHostCallback = this::setTiles; protected boolean mShowLabels = true; - protected boolean mSideLabels; + protected boolean mQSLabelFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -118,11 +118,12 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mQSLogger = qsLogger; mDumpManager = dumpManager; mFeatureFlags = featureFlags; + mQSLabelFlag = featureFlags.isQSLabelsEnabled(); } @Override protected void onInit() { - mView.initialize(mSideLabels); + mView.initialize(mQSLabelFlag); mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), ""); } @@ -197,7 +198,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr final TileRecord r = new TileRecord(); r.tile = tile; r.tileView = mHost.createTileView(tile, collapsedView); - r.tileView.setShowLabels(mShowLabels); mView.addTile(r); mRecords.add(r); mCachedSpecs = getTilesSpecs(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index a29ac3bb77e9..f51d7ef381ee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -55,6 +55,11 @@ public class QuickQSPanel extends QSPanel { applyBottomMargin((View) mRegularTileLayout); } + @Override + protected void inflateQSFooter(boolean newFooter) { + // No footer + } + private void applyBottomMargin(View view) { int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_bottom); MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams(); @@ -69,12 +74,22 @@ public class QuickQSPanel extends QSPanel { @Override public TileLayout createRegularTileLayout() { - return new QuickQSPanel.HeaderTileLayout(mContext); + if (mSideLabels) { + return new QQSSideLabelTileLayout(mContext); + } else { + return new QuickQSPanel.HeaderTileLayout(mContext); + } } @Override protected QSTileLayout createHorizontalTileLayout() { - return new DoubleLineTileLayout(mContext); + if (mSideLabels) { + TileLayout t = createRegularTileLayout(); + t.setMaxColumns(2); + return t; + } else { + return new DoubleLineTileLayout(mContext); + } } @Override @@ -331,4 +346,38 @@ public class QuickQSPanel extends QSPanel { } } } + + static class QQSSideLabelTileLayout extends SideLabelTileLayout { + QQSSideLabelTileLayout(Context context) { + super(context, null); + setClipChildren(false); + setClipToPadding(false); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_HORIZONTAL; + setLayoutParams(lp); + setMaxColumns(4); + } + + @Override + public boolean updateResources() { + boolean b = super.updateResources(); + mMaxAllowedRows = 2; + return b; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateResources(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Make sure to always use the correct number of rows. As it's determined by the + // columns, just use as many as needed. + updateMaxRows(10000, mRecords.size()); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 383e932a6955..671f8f7dd2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import com.android.internal.logging.MetricsLogger; @@ -42,8 +41,6 @@ import javax.inject.Named; @QSScope public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> { - private boolean mUseSideLabels; - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = newConfig -> { int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); @@ -58,12 +55,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, @Named(QUICK_QS_PANEL) MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, - FeatureFlags featureFlags + DumpManager dumpManager, FeatureFlags featureFlags ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); - mUseSideLabels = qsLabelsFlag; } @Override @@ -104,19 +99,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> break; } } - if (mUseSideLabels) { - List<QSTile> newTiles = new ArrayList<>(); - for (int i = 0; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - for (int i = 1; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - super.setTiles(newTiles, true); - - } else { - super.setTiles(tiles, true); - } + super.setTiles(tiles, !mQSLabelFlag); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java new file mode 100644 index 000000000000..42d603ec5051 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs; + +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.display.ColorDisplayManager; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.provider.Settings; + +import androidx.annotation.NonNull; + +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.CallbackController; +import com.android.systemui.util.settings.SecureSettings; + +import java.util.ArrayList; + +import javax.inject.Inject; + +/** + * @hide + */ +public class ReduceBrightColorsController implements + CallbackController<ReduceBrightColorsController.Listener> { + private final ColorDisplayManager mManager; + private final UserTracker mUserTracker; + private UserTracker.Callback mCurrentUserTrackerCallback; + private final Handler mHandler; + private final ContentObserver mContentObserver; + private final SecureSettings mSecureSettings; + private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>(); + + @Inject + public ReduceBrightColorsController(UserTracker userTracker, + @Background Handler handler, + ColorDisplayManager colorDisplayManager, + SecureSettings secureSettings) { + mManager = colorDisplayManager; + mUserTracker = userTracker; + mHandler = handler; + mSecureSettings = secureSettings; + mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + final String setting = uri == null ? null : uri.getLastPathSegment(); + synchronized (mListeners) { + if (setting != null && mListeners.size() != 0) { + if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) { + for (Listener listener : mListeners) { + listener.onActivated(mManager.isReduceBrightColorsActivated()); + } + } + } + } + } + }; + + mCurrentUserTrackerCallback = new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, Context userContext) { + synchronized (mListeners) { + if (mListeners.size() > 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + mSecureSettings.registerContentObserverForUser( + Settings.Secure.getUriFor( + Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED), + false, mContentObserver, newUser); + } + } + } + }; + mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler)); + } + + @Override + public void addCallback(@NonNull Listener listener) { + synchronized (mListeners) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + if (mListeners.size() == 1) { + mSecureSettings.registerContentObserverForUser( + Settings.Secure.getUriFor( + Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED), + false, mContentObserver, mUserTracker.getUserId()); + } + } + } + } + + @Override + public void removeCallback(@androidx.annotation.NonNull Listener listener) { + synchronized (mListeners) { + if (mListeners.remove(listener) && mListeners.size() == 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + } + } + } + + /** Returns {@code true} if Reduce Bright Colors is activated */ + public boolean isReduceBrightColorsActivated() { + return mManager.isReduceBrightColorsActivated(); + } + + /** Sets the activation state of Reduce Bright Colors */ + public void setReduceBrightColorsActivated(boolean activated) { + mManager.setReduceBrightColorsActivated(activated); + } + + /** + * Listener invoked whenever the Reduce Bright Colors settings are changed. + */ + public interface Listener { + /** + * Listener invoked when the activated state changes. + * + * @param activated {@code true} if Reduce Bright Colors is activated. + */ + default void onActivated(boolean activated) { + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index 74a7ac1cb6dd..52f111e7ab48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -18,22 +18,18 @@ package com.android.systemui.qs import android.content.Context import android.util.AttributeSet -import com.android.systemui.R -open class SideLabelTileLayout(context: Context, attrs: AttributeSet) : TileLayout(context, attrs) { +open class SideLabelTileLayout( + context: Context, + attrs: AttributeSet? +) : TileLayout(context, attrs) { override fun updateResources(): Boolean { return super.updateResources().also { - mResourceColumns = 2 mMaxAllowedRows = 4 - mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt() - mCellMarginVertical = mCellMarginHorizontal - mMaxCellHeight = context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) } } - override fun setShowLabels(show: Boolean) { } - override fun isFull(): Boolean { return mRecords.size >= maxTiles() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index d559e07f3ff6..c1ce4a577dda 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -36,7 +36,6 @@ public class TileLayout extends ViewGroup implements QSTileLayout { private int mCellMarginTop; protected boolean mListening; protected int mMaxAllowedRows = 3; - private boolean mShowLabels = true; // Prototyping with less rows private final boolean mLessRows; @@ -57,12 +56,6 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } @Override - public void setShowLabels(boolean show) { - mShowLabels = show; - updateResources(); - } - - @Override public int getOffsetTop(TileRecord tile) { return getTop(); } @@ -124,15 +117,13 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public boolean updateResources() { final Resources res = mContext.getResources(); mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + updateColumns(); mMaxCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); - if (!mShowLabels && mCellMarginVertical == 0) { - mCellMarginVertical = mCellMarginHorizontal; - } mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows)); - if (mLessRows && mShowLabels) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1); + if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1); if (updateColumns()) { requestLayout(); return true; @@ -193,9 +184,8 @@ public class TileLayout extends ViewGroup implements QSTileLayout { + mCellMarginVertical; final int previousRows = mRows; mRows = availableHeight / (getCellHeight() + mCellMarginVertical); - final int minRows = mShowLabels ? mMinRows : mMinRows + 1; - if (mRows < minRows) { - mRows = minRows; + if (mRows < mMinRows) { + mRows = mMinRows; } else if (mRows >= mMaxAllowedRows) { mRows = mMaxAllowedRows; } @@ -215,7 +205,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } protected int getCellHeight() { - return mShowLabels ? mMaxCellHeight : mMaxCellHeight / 2; + return mMaxCellHeight; } protected void layoutTileRecords(int numRecords) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java index beacf88cf354..0dc0b30748aa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java @@ -42,6 +42,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import java.util.function.Consumer; @@ -74,30 +75,25 @@ public class QSCarrierGroupController { private final NetworkController.SignalCallback mSignalCallback = new NetworkController.SignalCallback() { @Override - public void setMobileDataIndicators(NetworkController.IconState statusIcon, - NetworkController.IconState qsIcon, int statusType, int qsType, - boolean activityIn, boolean activityOut, int volteIcon, - CharSequence typeContentDescription, - CharSequence typeContentDescriptionHtml, CharSequence description, - boolean isWide, int subId, boolean roaming, boolean showTriangle) { + public void setMobileDataIndicators(MobileDataIndicators indicators) { if (mProviderModel) { return; } - int slotIndex = getSlotIndex(subId); + int slotIndex = getSlotIndex(indicators.subId); if (slotIndex >= SIM_SLOTS) { Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex); return; } if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { - Log.e(TAG, "Invalid SIM slot index for subscription: " + subId); + Log.e(TAG, "Invalid SIM slot index for subscription: " + indicators.subId); return; } mInfos[slotIndex] = new CellSignalState( - statusIcon.visible, - statusIcon.icon, - statusIcon.contentDescription, - typeContentDescription.toString(), - roaming + indicators.statusIcon.visible, + indicators.statusIcon.icon, + indicators.statusIcon.contentDescription, + indicators.typeContentDescription.toString(), + indicators.roaming ); mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java index 47cb45b14b9b..ce8f6c1737d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java @@ -16,19 +16,19 @@ package com.android.systemui.qs.customize; import android.content.Context; import android.view.View; -import android.widget.TextView; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.tileimpl.QSTileView; -public class CustomizeTileView extends QSTileView { +public class CustomizeTileView extends QSTileView implements TileAdapter.CustomizeView { private boolean mShowAppLabel; public CustomizeTileView(Context context, QSIconView icon) { super(context, icon); } + @Override public void setShowAppLabel(boolean showAppLabel) { mShowAppLabel = showAppLabel; mSecondLine.setVisibility(showAppLabel ? View.VISIBLE : View.GONE); @@ -41,10 +41,6 @@ public class CustomizeTileView extends QSTileView { mSecondLine.setVisibility(mShowAppLabel ? View.VISIBLE : View.GONE); } - public TextView getAppLabel() { - return mSecondLine; - } - @Override protected boolean animationsEnabled() { return false; @@ -54,4 +50,9 @@ public class CustomizeTileView extends QSTileView { public boolean isLongClickable() { return false; } + + @Override + public void changeState(QSTile.State state) { + handleStateChanged(state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt new file mode 100644 index 000000000000..f8c0dd4239d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt @@ -0,0 +1,44 @@ +package com.android.systemui.qs.customize + +import android.content.Context +import android.view.View +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.QSTileViewHorizontal + +/** + * Class for displaying tiles in [QSCustomizer] with the new design (labels on the side). + * + * This is a class parallel to [CustomizeTileView], but inheriting from [QSTileViewHorizontal]. + */ +class CustomizeTileViewHorizontal( + context: Context, + icon: QSIconView +) : QSTileViewHorizontal(context, icon), + TileAdapter.CustomizeView { + + private var showAppLabel = false + + override fun setShowAppLabel(showAppLabel: Boolean) { + this.showAppLabel = showAppLabel + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + mLabel.isSingleLine = showAppLabel + } + + override fun handleStateChanged(state: QSTile.State) { + super.handleStateChanged(state) + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + } + + override fun animationsEnabled(): Boolean { + return false + } + + override fun isLongClickable(): Boolean { + return false + } + + override fun changeState(state: QSTile.State) { + handleStateChanged(state) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 9fe949b15933..dce081f21581 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -25,7 +25,6 @@ import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; import android.widget.Toolbar; @@ -48,7 +47,6 @@ import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; public class QSCustomizer extends LinearLayout { static final int MENU_RESET = Menu.FIRST; - static final int MENU_REMOVE_LABELS = Menu.FIRST + 1; static final String EXTRA_QS_CUSTOMIZING = "qs_customizing"; private final QSDetailClipper mClipper; @@ -77,10 +75,6 @@ public class QSCustomizer extends LinearLayout { toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, mContext.getString(com.android.internal.R.string.reset)); - // Prototype menu item - toolbar.getMenu() - .add(Menu.NONE, MENU_REMOVE_LABELS, Menu.NONE, R.string.qs_remove_labels) - .setCheckable(true); toolbar.setTitle(R.string.qs_edit); mRecyclerView = findViewById(android.R.id.list); mTransparentView = findViewById(R.id.customizer_transparent_view); @@ -89,11 +83,6 @@ public class QSCustomizer extends LinearLayout { mRecyclerView.setItemAnimator(animator); } - MenuItem getRemoveItem() { - return ((Toolbar) findViewById(com.android.internal.R.id.action_bar)) - .getMenu().findItem(MENU_REMOVE_LABELS); - } - void updateResources() { LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams(); lp.height = mContext.getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index d4bab2197249..f56a2bbefaf7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -17,9 +17,7 @@ package com.android.systemui.qs.customize; import static com.android.systemui.qs.customize.QSCustomizer.EXTRA_QS_CUSTOMIZING; -import static com.android.systemui.qs.customize.QSCustomizer.MENU_REMOVE_LABELS; import static com.android.systemui.qs.customize.QSCustomizer.MENU_RESET; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import android.content.res.Configuration; import android.os.Bundle; @@ -38,7 +36,6 @@ import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSFragment; -import com.android.systemui.qs.QSPanelController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.statusbar.phone.LightBarController; @@ -46,14 +43,12 @@ import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.tuner.TunerService; import com.android.systemui.util.ViewController; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; -import javax.inject.Named; /** {@link ViewController} for {@link QSCustomizer}. */ @QSScope @@ -67,8 +62,6 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { private final ConfigurationController mConfigurationController; private final UiEventLogger mUiEventLogger; private final Toolbar mToolbar; - private final TunerService mTunerService; - private final boolean mQsLabelsFlag; private final OnMenuItemClickListener mOnMenuItemClickListener = new OnMenuItemClickListener() { @Override @@ -76,11 +69,6 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { if (item.getItemId() == MENU_RESET) { mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET); reset(); - } else if (item.getItemId() == MENU_REMOVE_LABELS) { - item.setChecked(!item.isChecked()); - mTunerService.setValue( - QSPanelController.QS_REMOVE_LABELS, item.isChecked() ? "1" : "0"); - return false; } return false; } @@ -111,20 +99,11 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { } }; - private final TunerService.Tunable mTunable = new TunerService.Tunable() { - @Override - public void onTuningChanged(String key, String newValue) { - mToolbar.getMenu().findItem(MENU_REMOVE_LABELS) - .setChecked(newValue != null && !("0".equals(newValue))); - } - }; - @Inject protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper, QSTileHost qsTileHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle, KeyguardStateController keyguardStateController, LightBarController lightBarController, - ConfigurationController configurationController, UiEventLogger uiEventLogger, - TunerService tunerService, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) { + ConfigurationController configurationController, UiEventLogger uiEventLogger) { super(view); mTileQueryHelper = tileQueryHelper; mQsTileHost = qsTileHost; @@ -136,21 +115,12 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { mUiEventLogger = uiEventLogger; mToolbar = mView.findViewById(com.android.internal.R.id.action_bar); - mQsLabelsFlag = qsLabelsFlag; - - mTunerService = tunerService; } - @Override - protected void onInit() { - super.onInit(); - mView.getRemoveItem().setVisible(mQsLabelsFlag); - } @Override protected void onViewAttached() { mView.updateNavBackDrop(getResources().getConfiguration(), mLightBarController); - mTunerService.addTunable(mTunable, QSPanelController.QS_REMOVE_LABELS); mConfigurationController.addCallback(mConfigurationListener); @@ -181,7 +151,6 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { @Override protected void onViewDetached() { - mTunerService.removeTunable(mTunable); mTileQueryHelper.setListener(null); mToolbar.setOnMenuItemClickListener(null); mConfigurationController.removeCallback(mConfigurationListener); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 048fdc3a0e5a..0adc8448b89f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -14,6 +14,8 @@ package com.android.systemui.qs.customize; +import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; + import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -30,6 +32,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; @@ -41,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.customize.TileAdapter.Holder; @@ -49,11 +53,13 @@ import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; +import com.android.systemui.qs.tileimpl.QSTileView; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; /** */ @QSScope @@ -75,6 +81,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private static final int ACTION_ADD = 1; private static final int ACTION_MOVE = 2; + private static final int NUM_COLUMNS_ID = R.integer.quick_settings_num_columns; + private final Context mContext; private final Handler mHandler = new Handler(); @@ -87,6 +95,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private int mEditIndex; private int mTileDividerIndex; private int mFocusIndex; + private boolean mNeedsFocus; private List<String> mCurrentSpecs; private List<TileInfo> mOtherTiles; @@ -99,9 +108,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private final AccessibilityDelegateCompat mAccessibilityDelegate; private RecyclerView mRecyclerView; private int mNumColumns; + private final boolean mUseHorizontalTiles; @Inject - public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) { + public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger, + @Named(QS_LABELS_FLAG) boolean useHorizontalTiles) { mContext = context; mHost = qsHost; mUiEventLogger = uiEventLogger; @@ -109,8 +120,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mDecoration = new TileItemDecoration(context); mMarginDecoration = new MarginTileDecoration(); mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); - mNumColumns = context.getResources().getInteger(R.integer.quick_settings_num_columns); + mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID); mAccessibilityDelegate = new TileAdapterDelegate(); + mUseHorizontalTiles = useHorizontalTiles; } @Override @@ -129,7 +141,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta * @return {@code true} if the number of columns changed, {@code false} otherwise */ public boolean updateNumColumns() { - int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns); + int numColumns = mContext.getResources().getInteger(NUM_COLUMNS_ID); if (numColumns != mNumColumns) { mNumColumns = numColumns; return true; @@ -268,7 +280,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent, false); - frame.addView(new CustomizeTileView(context, new QSIconViewImpl(context))); + View view = mUseHorizontalTiles + ? new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context)) + : new CustomizeTileView(context, new QSIconViewImpl(context)); + frame.addView(view); return new Holder(frame); } @@ -351,8 +366,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } info.state.expandedAccessibilityClassName = ""; - holder.mTileView.handleStateChanged(info.state); - holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem); + // The holder has a tileView, therefore this call is not null + holder.getTileAsCustomizeView().changeState(info.state); + holder.getTileAsCustomizeView().setShowAppLabel(position > mEditIndex && !info.isSystem); holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); holder.mTileView.setClickable(true); holder.mTileView.setOnClickListener(null); @@ -531,25 +547,34 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } public class Holder extends ViewHolder { - private CustomizeTileView mTileView; + private QSTileView mTileView; public Holder(View itemView) { super(itemView); if (itemView instanceof FrameLayout) { - mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0); - mTileView.setBackground(null); + mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.setBackground(null); + } mTileView.getIcon().disableAnimation(); mTileView.setTag(this); ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate); } } + @Nullable + public CustomizeView getTileAsCustomizeView() { + return (CustomizeView) mTileView; + } + public void clearDrag() { itemView.clearAnimation(); - mTileView.findViewById(R.id.tile_label).clearAnimation(); - mTileView.findViewById(R.id.tile_label).setAlpha(1); - mTileView.getAppLabel().clearAnimation(); - mTileView.getAppLabel().setAlpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).clearAnimation(); + mTileView.findViewById(R.id.tile_label).setAlpha(1); + mTileView.getAppLabel().clearAnimation(); + mTileView.getAppLabel().setAlpha(.6f); + } } public void startDrag() { @@ -557,12 +582,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(DRAG_SCALE) .scaleY(DRAG_SCALE); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(0); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + } } public void stopDrag() { @@ -570,12 +597,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(1) .scaleY(1); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(1); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(1); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(.6f); + } } boolean canRemove() { @@ -719,7 +748,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta int position = mCurrentDrag.getAdapterPosition(); if (position == RecyclerView.NO_POSITION) return; TileInfo info = mTiles.get(position); - mCurrentDrag.mTileView.setShowAppLabel( + ((CustomizeView) mCurrentDrag.mTileView).setShowAppLabel( position > mEditIndex && !info.isSystem); mCurrentDrag.stopDrag(); mCurrentDrag = null; @@ -779,4 +808,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta public void onSwiped(ViewHolder viewHolder, int direction) { } }; + + interface CustomizeView { + void setShowAppLabel(boolean showAppLabel); + void changeState(@NonNull QSTile.State state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java index 35a8257bd5a7..10192bc20df9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java @@ -21,6 +21,7 @@ import android.hardware.display.ColorDisplayManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.util.settings.GlobalSettings; import javax.inject.Named; @@ -31,6 +32,8 @@ import dagger.Provides; public interface QSFlagsModule { String QS_LABELS_FLAG = "qs_labels_flag"; String RBC_AVAILABLE = "rbc_available"; + String PM_LITE_ENABLED = "pm_lite"; + String PM_LITE_SETTING = "sysui_pm_lite"; @Provides @SysUISingleton @@ -46,4 +49,11 @@ public interface QSFlagsModule { static boolean isReduceBrightColorsAvailable(Context context) { return ColorDisplayManager.isReduceBrightColorsAvailable(context); } + + @Provides + @SysUISingleton + @Named(PM_LITE_ENABLED) + static boolean isPMLiteEnabled(FeatureFlags featureFlags, GlobalSettings globalSettings) { + return featureFlags.isPMLiteEnabled() && globalSettings.getInt(PM_LITE_SETTING, 0) != 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 33713f3724c7..d41bd7ad4d3c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.dagger; +import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; + import android.content.Context; import android.hardware.display.NightDisplayListener; import android.os.Handler; @@ -25,6 +27,7 @@ import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.policy.CastController; @@ -32,6 +35,8 @@ import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.util.settings.SecureSettings; +import javax.inject.Named; + import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -54,7 +59,9 @@ public interface QSModule { DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, - CastController castController) { + CastController castController, + ReduceBrightColorsController reduceBrightColorsController, + @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { AutoTileManager manager = new AutoTileManager( context, autoAddTrackerBuilder, @@ -65,7 +72,9 @@ public interface QSModule { dataSaverController, managedProfileController, nightDisplayListener, - castController + castController, + reduceBrightColorsController, + isReduceBrightColorsAvailable ); manager.init(); return manager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 6983b38489f6..9e5fe732cd0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -30,6 +30,7 @@ import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tiles.AirplaneModeTile; +import com.android.systemui.qs.tiles.AlarmTile; import com.android.systemui.qs.tiles.BatterySaverTile; import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CameraToggleTile; @@ -91,6 +92,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider<CameraToggleTile> mCameraToggleTileProvider; private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider; private final Provider<DeviceControlsTile> mDeviceControlsTileProvider; + private final Provider<AlarmTile> mAlarmTileProvider; private final Lazy<QSHost> mQsHostLazy; private final Provider<CustomTile.Builder> mCustomTileBuilderProvider; @@ -126,7 +128,8 @@ public class QSFactoryImpl implements QSFactory { Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider, Provider<CameraToggleTile> cameraToggleTileProvider, Provider<MicrophoneToggleTile> microphoneToggleTileProvider, - Provider<DeviceControlsTile> deviceControlsTileProvider) { + Provider<DeviceControlsTile> deviceControlsTileProvider, + Provider<AlarmTile> alarmTileProvider) { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; @@ -157,6 +160,7 @@ public class QSFactoryImpl implements QSFactory { mCameraToggleTileProvider = cameraToggleTileProvider; mMicrophoneToggleTileProvider = microphoneToggleTileProvider; mDeviceControlsTileProvider = deviceControlsTileProvider; + mAlarmTileProvider = alarmTileProvider; } public QSTile createTile(String tileSpec) { @@ -218,6 +222,8 @@ public class QSFactoryImpl implements QSFactory { return mMicrophoneToggleTileProvider.get(); case "controls": return mDeviceControlsTileProvider.get(); + case "alarm": + return mAlarmTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 38e2ba4df79a..33ca7d6bafd8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -259,23 +259,30 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); final StringBuilder stateDescription = new StringBuilder(); + String text = ""; switch (state.state) { case Tile.STATE_UNAVAILABLE: - stateDescription.append(mContext.getString(R.string.tile_unavailable)); + text = mContext.getString(R.string.tile_unavailable); break; case Tile.STATE_INACTIVE: if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_off)); + text = mContext.getString(R.string.switch_bar_off); } break; case Tile.STATE_ACTIVE: if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_on)); + text = mContext.getString(R.string.switch_bar_on); } break; default: break; } + if (!TextUtils.isEmpty(text)) { + stateDescription.append(text); + if (TextUtils.isEmpty(state.secondaryLabel)) { + state.secondaryLabel = text; + } + } if (!TextUtils.isEmpty(state.stateDescription)) { stateDescription.append(", "); stateDescription.append(state.stateDescription); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index a699e2ec7cfc..b59326ae56d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -105,22 +105,25 @@ public class QSTileView extends QSTileBaseView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mLabel.setSingleLine(false); super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // Remeasure view if the primary label requires more then 2 lines or the secondary label - // text will be cut off. - if (mLabel.getLineCount() > mMaxLabelLines || !TextUtils.isEmpty(mSecondLine.getText()) - && mSecondLine.getLineHeight() > mSecondLine.getHeight()) { - if (!mLabel.isSingleLine()) { - mLabel.setSingleLine(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } else { - if (mLabel.isSingleLine()) { - mLabel.setSingleLine(false); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } + // Remeasure view if the primary label requires more than mMaxLabelLines lines or the + // secondary label text will be cut off. + if (shouldLabelBeSingleLine()) { + mLabel.setSingleLine(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private boolean shouldLabelBeSingleLine() { + if (mLabel.getLineCount() > mMaxLabelLines) { + return true; + } else if (!TextUtils.isEmpty(mSecondLine.getText()) + && mLabel.getLineCount() > mMaxLabelLines - 1) { + return true; } + return false; } @Override @@ -172,8 +175,7 @@ public class QSTileView extends QSTileBaseView { mLabelContainer.setLongClickable(false); } - @Override - public void setShowLabels(boolean show) { - mHandler.post(() -> mLabelContainer.setVisibility(show ? VISIBLE : GONE)); + public TextView getAppLabel() { + return mSecondLine; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index dc81b702021f..328c2c353a29 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -16,28 +16,34 @@ package com.android.systemui.qs.tileimpl +import android.animation.ValueAnimator import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.Drawable -import android.graphics.drawable.PaintDrawable -import android.graphics.drawable.RippleDrawable +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RoundRectShape import android.service.quicksettings.Tile.STATE_ACTIVE import android.view.Gravity -import android.view.View import android.widget.LinearLayout +import android.widget.RelativeLayout import com.android.systemui.R import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState -class QSTileViewHorizontal( +// Placeholder +private const val CORNER_RADIUS = 40f +private val RADII = (1..8).map { CORNER_RADIUS }.toFloatArray() + +open class QSTileViewHorizontal( context: Context, icon: QSIconView ) : QSTileView(context, icon, false) { - private var paintDrawable: PaintDrawable? = null - private var divider: View? = null + protected var backgroundDrawable: ShapeDrawable? = null + private var paintColor = Color.WHITE + private var paintAnimator: ValueAnimator? = null init { orientation = HORIZONTAL @@ -49,7 +55,12 @@ class QSTileViewHorizontal( override fun createLabel() { super.createLabel() - findViewById<LinearLayout>(R.id.label_group)?.gravity = Gravity.START + findViewById<LinearLayout>(R.id.label_group)?.apply { + gravity = Gravity.START + (layoutParams as? RelativeLayout.LayoutParams)?.apply { + removeRule(RelativeLayout.ALIGN_PARENT_TOP) + } + } mLabel.gravity = Gravity.START mLabel.textDirection = TEXT_DIRECTION_LOCALE mSecondLine.gravity = Gravity.START @@ -57,43 +68,57 @@ class QSTileViewHorizontal( val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding) mLabelContainer.setPaddingRelative(0, padding, padding, padding) (mLabelContainer.layoutParams as LayoutParams).gravity = - Gravity.CENTER_VERTICAL or Gravity.START + Gravity.CENTER_VERTICAL or Gravity.START } override fun updateRippleSize() { } override fun newTileBackground(): Drawable? { - val d = super.newTileBackground() - if (paintDrawable == null) { - paintDrawable = PaintDrawable(Color.WHITE).apply { - setCornerRadius(50f) - } - } - if (d is RippleDrawable) { - d.addLayer(paintDrawable) - return d - } else { - return paintDrawable - } + backgroundDrawable = ShapeDrawable(RoundRectShape(RADII, null, null)) + return backgroundDrawable } override fun setClickable(clickable: Boolean) { super.setClickable(clickable) background = mTileBackground - if (clickable && mShowRippleEffect) { - mRipple?.setHotspotBounds(left, top, right, bottom) - } else { - mRipple?.setHotspotBounds(0, 0, 0, 0) - } } override fun handleStateChanged(state: QSTile.State) { super.handleStateChanged(state) - paintDrawable?.setTint(getCircleColor(state.state)) mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null - divider?.backgroundTintList = mLabel.textColors + + val allowAnimations = animationsEnabled() && paintColor != Color.WHITE + val newColor = getCircleColor(state.state) + if (allowAnimations) { + animateToNewState(newColor) + } else { + if (newColor != paintColor) { + clearAnimator() + backgroundDrawable?.setTintList(ColorStateList.valueOf(newColor)) + paintColor = newColor + } + } + } + + private fun animateToNewState(newColor: Int) { + if (newColor != paintColor) { + clearAnimator() + paintAnimator = ValueAnimator.ofArgb(paintColor, newColor) + .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { + addUpdateListener { animation: ValueAnimator -> + val c = animation.animatedValue as Int + backgroundDrawable?.setTintList(ColorStateList.valueOf(c)) + paintColor = c + } + start() + } + } + } + + private fun clearAnimator() { + paintAnimator?.cancel()?.also { paintAnimator = null } } override fun handleExpand(dualTarget: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt new file mode 100644 index 000000000000..dede627cd4dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -0,0 +1,117 @@ +package com.android.systemui.qs.tiles + +import android.app.AlarmManager +import android.app.AlarmManager.AlarmClockInfo +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.provider.AlarmClock +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.text.format.DateFormat +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.MetricsLogger +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.statusbar.policy.NextAlarmController +import java.util.Locale +import javax.inject.Inject + +class AlarmTile @Inject constructor( + host: QSHost, + @Background backgroundLooper: Looper, + @Main mainHandler: Handler, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger, + private val featureFlags: FeatureFlags, + private val userTracker: UserTracker, + nextAlarmController: NextAlarmController +) : QSTileImpl<QSTile.State>( + host, + backgroundLooper, + mainHandler, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger +) { + + private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null + private val icon = ResourceIcon.get(R.drawable.ic_alarm) + @VisibleForTesting + internal val defaultIntent = Intent(AlarmClock.ACTION_SET_ALARM) + private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm -> + lastAlarmInfo = nextAlarm + refreshState() + } + + init { + nextAlarmController.observe(this, callback) + } + + override fun isAvailable(): Boolean { + return featureFlags.isAlarmTileAvailable + } + + override fun newTileState(): QSTile.State { + return QSTile.State().apply { + handlesLongClick = false + } + } + + private fun startDefaultSetAlarm() { + mActivityStarter.postStartActivityDismissingKeyguard(defaultIntent, 0) + } + + override fun handleClick() { + lastAlarmInfo?.showIntent?.let { + mActivityStarter.postStartActivityDismissingKeyguard(it) + } ?: startDefaultSetAlarm() + } + + override fun handleUpdateState(state: QSTile.State, arg: Any?) { + state.icon = icon + state.label = tileLabel + lastAlarmInfo?.let { + state.secondaryLabel = formatNextAlarm(it) + state.state = Tile.STATE_ACTIVE + } ?: run { + state.secondaryLabel = mContext.getString(R.string.qs_alarm_tile_no_alarm) + state.state = Tile.STATE_INACTIVE + } + state.contentDescription = TextUtils.concat(state.label, ", ", state.secondaryLabel) + } + + override fun getTileLabel(): CharSequence { + return mContext.getString(R.string.status_bar_alarm) + } + + private fun formatNextAlarm(info: AlarmClockInfo): String { + val skeleton = if (use24HourFormat()) "EHm" else "Ehma" + val pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton) + return DateFormat.format(pattern, info.triggerTime).toString() + } + + private fun use24HourFormat(): Boolean { + return DateFormat.is24HourFormat(mContext, userTracker.userId) + } + + override fun getMetricsCategory(): Int { + return 0 + } + + override fun getLongClickIntent(): Intent? { + return null + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 3841daca7ebe..70287cd37d01 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; +import static android.content.pm.PackageManager.FEATURE_CAMERA_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,8 +61,8 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return /*getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) - && */whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "camera_toggle_enabled", false)); } @@ -75,7 +78,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return CAMERA; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index becbfd57c14b..e7d2fc7c0ae4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -55,6 +55,7 @@ import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -271,18 +272,15 @@ public class CastTile extends QSTileImpl<BooleanState> { private final NetworkController.SignalCallback mSignalCallback = new NetworkController.SignalCallback() { @Override - public void setWifiIndicators(boolean enabled, - NetworkController.IconState statusIcon, - NetworkController.IconState qsIcon, boolean activityIn, boolean activityOut, - String description, boolean isTransient, String statusLabel) { + public void setWifiIndicators(WifiIndicators indicators) { // statusIcon.visible has the connected status information if(SystemProperties.getBoolean(WFD_ENABLE, false)) { - if(enabled != mWifiConnected) { - mWifiConnected = enabled; + if(indicators.enabled != mWifiConnected) { + mWifiConnected = indicators.enabled; refreshState(); } } else { - boolean enabledAndConnected = enabled && qsIcon.visible; + boolean enabledAndConnected = indicators.enabled && indicators.qsIcon.visible; if (enabledAndConnected != mWifiConnected) { mWifiConnected = enabledAndConnected; // Hotspot is not connected, so changes here should update diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index eae051550ebf..6a574d1d314b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -56,6 +56,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import javax.inject.Inject; @@ -264,21 +265,17 @@ public class CellularTile extends QSTileImpl<SignalState> { private final CallbackInfo mInfo = new CallbackInfo(); @Override - public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, - int qsType, boolean activityIn, boolean activityOut, int volteIcon, - CharSequence typeContentDescription, - CharSequence typeContentDescriptionHtml, CharSequence description, - boolean isWide, int subId, boolean roaming, boolean showTriangle) { - if (qsIcon == null) { + public void setMobileDataIndicators(MobileDataIndicators indicators) { + if (indicators.qsIcon == null) { // Not data sim, don't display. return; } mInfo.dataSubscriptionName = mController.getMobileDataNetworkName(); - mInfo.dataContentDescription = - (description != null) ? typeContentDescriptionHtml : null; - mInfo.activityIn = activityIn; - mInfo.activityOut = activityOut; - mInfo.roaming = roaming; + mInfo.dataContentDescription = indicators.description != null + ? indicators.typeContentDescriptionHtml : null; + mInfo.activityIn = indicators.activityIn; + mInfo.activityOut = indicators.activityOut; + mInfo.roaming = indicators.roaming; mInfo.multipleSubs = mController.getNumberSubscriptions() > 1; refreshState(mInfo); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 41445917a011..3b9f5dc8ea03 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -25,7 +25,7 @@ import com.android.internal.logging.MetricsLogger import com.android.systemui.R import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.dagger.ControlsComponent -import com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE +import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsDialog import com.android.systemui.dagger.qualifiers.Background @@ -92,7 +92,7 @@ class DeviceControlsTile @Inject constructor( override fun isAvailable(): Boolean { return featureFlags.isKeyguardLayoutEnabled && controlsLockscreen && - controlsComponent.getVisibility() != UNAVAILABLE + controlsComponent.getControlsController().isPresent } override fun newTileState(): QSTile.State { @@ -119,7 +119,7 @@ class DeviceControlsTile @Inject constructor( } override fun handleClick() { - if (state.state != Tile.STATE_UNAVAILABLE) { + if (state.state == Tile.STATE_ACTIVE) { mUiHandler.post { createDialog() controlsDialog?.show(controlsComponent.getControlsUiController().get()) @@ -129,15 +129,21 @@ class DeviceControlsTile @Inject constructor( override fun handleUpdateState(state: QSTile.State, arg: Any?) { state.label = tileLabel - state.secondaryLabel = "" - state.stateDescription = "" + state.contentDescription = state.label state.icon = icon - if (hasControlsApps.get()) { - state.state = Tile.STATE_ACTIVE + if (controlsComponent.isEnabled() && hasControlsApps.get()) { if (controlsDialog == null) { mUiHandler.post(this::createDialog) } + if (controlsComponent.getVisibility() == AVAILABLE) { + state.state = Tile.STATE_ACTIVE + state.secondaryLabel = "" + } else { + state.state = Tile.STATE_INACTIVE + state.secondaryLabel = mContext.getText(R.string.controls_tile_locked) + } + state.stateDescription = state.secondaryLabel } else { state.state = Tile.STATE_UNAVAILABLE dismissDialog() 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 49dd84165979..e1a1fd2679c5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -51,7 +51,9 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators; import com.android.systemui.statusbar.policy.WifiIcons; import java.io.FileDescriptor; @@ -234,70 +236,44 @@ public class InternetTile extends QSTileImpl<SignalState> { @Override - public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, - boolean activityIn, boolean activityOut, String description, boolean isTransient, - String statusLabel) { + public void setWifiIndicators(WifiIndicators indicators) { if (DEBUG) { - Log.d(TAG, "setWifiIndicators: " - + "enabled = " + enabled + "," - + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + "," - + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + "," - + "activityIn = " + activityIn + "," - + "activityOut = " + activityOut + "," - + "description = " + description + "," - + "isTransient = " + isTransient + "," - + "statusLabel = " + statusLabel); + Log.d(TAG, "setWifiIndicators: " + indicators); } - mWifiInfo.mEnabled = enabled; - if (qsIcon == null) { + mWifiInfo.mEnabled = indicators.enabled; + if (indicators.qsIcon == null) { return; } - mWifiInfo.mConnected = qsIcon.visible; - mWifiInfo.mWifiSignalIconId = qsIcon.icon; - mWifiInfo.mWifiSignalContentDescription = qsIcon.contentDescription; - mWifiInfo.mSsid = description; - mWifiInfo.mActivityIn = activityIn; - mWifiInfo.mActivityOut = activityOut; - mWifiInfo.mIsTransient = isTransient; - mWifiInfo.mStatusLabel = statusLabel; + mWifiInfo.mConnected = indicators.qsIcon.visible; + mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon; + mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription; + mWifiInfo.mEnabled = indicators.enabled; + mWifiInfo.mSsid = indicators.description; + mWifiInfo.mActivityIn = indicators.activityIn; + mWifiInfo.mActivityOut = indicators.activityOut; + mWifiInfo.mIsTransient = indicators.isTransient; + mWifiInfo.mStatusLabel = indicators.statusLabel; refreshState(mWifiInfo); } @Override - public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, - int qsType, boolean activityIn, boolean activityOut, int volteIcon, - CharSequence typeContentDescription, - CharSequence typeContentDescriptionHtml, CharSequence description, - boolean isWide, int subId, boolean roaming, boolean showTriangle) { + public void setMobileDataIndicators(MobileDataIndicators indicators) { if (DEBUG) { - Log.d(TAG, "setMobileDataIndicators: " - + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + "," - + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + "," - + "statusType = " + statusType + "," - + "qsType = " + qsType + "," - + "activityIn = " + activityIn + "," - + "activityOut = " + activityOut + "," - + "typeContentDescription = " + typeContentDescription + "," - + "typeContentDescriptionHtml = " + typeContentDescriptionHtml + "," - + "description = " + description + "," - + "isWide = " + isWide + "," - + "subId = " + subId + "," - + "roaming = " + roaming + "," - + "showTriangle = " + showTriangle); + Log.d(TAG, "setMobileDataIndicators: " + indicators); } - if (qsIcon == null) { + if (indicators.qsIcon == null) { // Not data sim, don't display. return; } - mCellularInfo.mDataSubscriptionName = - description == null ? mController.getMobileDataNetworkName() : description; - mCellularInfo.mDataContentDescription = - (description != null) ? typeContentDescriptionHtml : null; - mCellularInfo.mMobileSignalIconId = qsIcon.icon; - mCellularInfo.mQsTypeIcon = qsType; - mCellularInfo.mActivityIn = activityIn; - mCellularInfo.mActivityOut = activityOut; - mCellularInfo.mRoaming = roaming; + mCellularInfo.mDataSubscriptionName = indicators.description == null + ? mController.getMobileDataNetworkName() : indicators.description; + mCellularInfo.mDataContentDescription = indicators.description != null + ? indicators.typeContentDescriptionHtml : null; + mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon; + mCellularInfo.mQsTypeIcon = indicators.qsType; + mCellularInfo.mActivityIn = indicators.activityIn; + mCellularInfo.mActivityOut = indicators.activityOut; + mCellularInfo.mRoaming = indicators.roaming; mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1; refreshState(mCellularInfo); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index 2f0071a1f198..e9b712df2154 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; +import static android.content.pm.PackageManager.FEATURE_MICROPHONE_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,7 +61,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager() + .hasSystemFeature(FEATURE_MICROPHONE_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "mic_toggle_enabled", false)); } @@ -74,7 +79,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return MICROPHONE; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index f94cabcee297..aec7b9a4b6b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -33,46 +33,39 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.SecureSetting; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.util.settings.SecureSettings; import javax.inject.Inject; import javax.inject.Named; /** Quick settings tile: Reduce Bright Colors **/ -public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { +public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> + implements ReduceBrightColorsController.Listener{ //TODO(b/170973645): get icon drawable private final Icon mIcon = null; - private final SecureSetting mActivatedSetting; private final boolean mIsAvailable; + private final ReduceBrightColorsController mReduceBrightColorsController; + private boolean mIsListening; @Inject public ReduceBrightColorsTile( @Named(RBC_AVAILABLE) boolean isAvailable, + ReduceBrightColorsController reduceBrightColorsController, QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, - QSLogger qsLogger, - UserTracker userTracker, - SecureSettings secureSettings + QSLogger qsLogger ) { super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, activityStarter, qsLogger); - - mActivatedSetting = new SecureSetting(secureSettings, mainHandler, - Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, userTracker.getUserId()) { - @Override - protected void handleValueChanged(int value, boolean observedChange) { - refreshState(); - } - }; + mReduceBrightColorsController = reduceBrightColorsController; + mReduceBrightColorsController.observe(getLifecycle(), this); mIsAvailable = isAvailable; } @@ -84,7 +77,6 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { @Override protected void handleDestroy() { super.handleDestroy(); - mActivatedSetting.setListening(false); } @Override @@ -93,25 +85,13 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { } @Override - public void handleSetListening(boolean listening) { - super.handleSetListening(listening); - mActivatedSetting.setListening(listening); - } - - @Override - protected void handleUserSwitch(int newUserId) { - mActivatedSetting.setUserId(newUserId); - refreshState(); - } - - @Override public Intent getLongClickIntent() { return new Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS); } @Override protected void handleClick() { - mActivatedSetting.setValue(mState.value ? 0 : 1); + mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value); } @Override @@ -121,7 +101,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { @Override protected void handleUpdateState(BooleanState state, Object arg) { - state.value = mActivatedSetting.getValue() == 1; + state.value = mReduceBrightColorsController.isReduceBrightColorsActivated(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_reduce_bright_colors_label); state.expandedAccessibilityClassName = Switch.class.getName(); @@ -132,4 +112,9 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { public int getMetricsCategory() { return 0; } + + @Override + public void onActivated(boolean activated) { + refreshState(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 4bf27e2aa4b8..c46cc4f9aa0a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -71,7 +71,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { @Override public Intent getLongClickIntent() { - return new Intent(Settings.ACTION_DISPLAY_SETTINGS); + return new Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 00703e7f8403..0c582bdbe12f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -17,7 +17,7 @@ package com.android.systemui.qs.tiles; import android.content.Intent; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.service.quicksettings.Tile; @@ -49,7 +49,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS /** * @return Id of the sensor that will be toggled */ - public abstract @IndividualSensor int getSensorId(); + public abstract @Sensor int getSensorId(); /** * @return icon for the QS tile diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index a6fd01108fad..341e67c9393f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -51,8 +51,8 @@ import com.android.systemui.qs.tileimpl.QSIconViewImpl; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.AccessPointController; -import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators; import com.android.systemui.statusbar.policy.WifiIcons; import com.android.wifitrackerlib.WifiEntry; @@ -303,22 +303,20 @@ public class WifiTile extends QSTileImpl<SignalState> { final CallbackInfo mInfo = new CallbackInfo(); @Override - public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, - boolean activityIn, boolean activityOut, String description, boolean isTransient, - String statusLabel) { - if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + enabled); - if (qsIcon == null) { + public void setWifiIndicators(WifiIndicators indicators) { + if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + indicators.enabled); + if (indicators.qsIcon == null) { return; } - mInfo.enabled = enabled; - mInfo.connected = qsIcon.visible; - mInfo.wifiSignalIconId = qsIcon.icon; - mInfo.ssid = description; - mInfo.activityIn = activityIn; - mInfo.activityOut = activityOut; - mInfo.wifiSignalContentDescription = qsIcon.contentDescription; - mInfo.isTransient = isTransient; - mInfo.statusLabel = statusLabel; + mInfo.enabled = indicators.enabled; + mInfo.connected = indicators.qsIcon.visible; + mInfo.wifiSignalIconId = indicators.qsIcon.icon; + mInfo.ssid = indicators.description; + mInfo.activityIn = indicators.activityIn; + mInfo.activityOut = indicators.activityOut; + mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription; + mInfo.isTransient = indicators.isTransient; + mInfo.statusLabel = indicators.statusLabel; if (isShowingDetail()) { mDetailAdapter.updateItems(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 5b2a7e7ff617..a87bfd83916a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -85,6 +85,7 @@ import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.ISplitScreenListener; +import com.android.systemui.shared.recents.IStartingWindowListener; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -101,6 +102,7 @@ import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.transition.RemoteTransitions; import java.io.FileDescriptor; @@ -150,6 +152,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final Optional<OneHanded> mOneHandedOptional; private final CommandQueue mCommandQueue; private final RemoteTransitions mShellTransitions; + private final Optional<StartingSurface> mStartingSurface; private Region mActiveNavBarRegion; @@ -167,6 +170,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private boolean mSupportsRoundedCornersOnWindows; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; private final ArraySet<IRemoteTransition> mRemoteTransitions = new ArraySet<>(); + private IStartingWindowListener mIStartingWindowListener; @VisibleForTesting public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -440,6 +444,21 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override + public void setStartingWindowListener(IStartingWindowListener listener) { + if (!verifyCaller("setStartingWindowListener")) { + return; + } + mIStartingWindowListener = listener; + final long token = Binder.clearCallingIdentity(); + try { + mStartingSurface.ifPresent(s -> + s.setStartingWindowListener(mStartingWindowListener)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { if (!verifyCaller("onQuickSwitchToNewTask")) { return; @@ -662,14 +681,29 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void startIntent(PendingIntent intent, int stage, int position, Bundle options) { + public void startIntent(PendingIntent intent, Intent fillInIntent, + int stage, int position, Bundle options) { if (!verifyCaller("startIntent")) { return; } final long token = Binder.clearCallingIdentity(); try { mSplitScreenOptional.ifPresent(s -> - s.startIntent(intent, stage, position, options)); + s.startIntent(intent, mContext, fillInIntent, stage, position, options)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void removeFromSideStage(int taskId) { + if (!verifyCaller("removeFromSideStage")) { + return; + } + final long token = Binder.clearCallingIdentity(); + try { + mSplitScreenOptional.ifPresent( + s -> s.removeFromSideStage(taskId)); } finally { Binder.restoreCallingIdentity(token); } @@ -770,6 +804,9 @@ public class OverviewProxyService extends CurrentUserTracker implements private final Consumer<Boolean> mPinnedStackAnimationCallback = this::notifyPinnedStackAnimationStarted; + private final BiConsumer<Integer, Integer> mStartingWindowListener = + this::notifyTaskLaunching; + // This is the death handler for the binder from the launcher service private final IBinder.DeathRecipient mOverviewServiceDeathRcpt = this::cleanupAfterDeath; @@ -789,10 +826,10 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void onTaskStageChanged(int taskId, int stage) { + public void onTaskStageChanged(int taskId, int stage, boolean visible) { try { if (mISplitScreenListener != null) { - mISplitScreenListener.onTaskStageChanged(taskId, stage); + mISplitScreenListener.onTaskStageChanged(taskId, stage, visible); } } catch (RemoteException e) { Log.e(TAG_OPS, "onTaskStageChanged", e); @@ -812,7 +849,8 @@ public class OverviewProxyService extends CurrentUserTracker implements Optional<Lazy<StatusBar>> statusBarOptionalLazy, Optional<OneHanded> oneHandedOptional, BroadcastDispatcher broadcastDispatcher, - RemoteTransitions shellTransitions) { + RemoteTransitions shellTransitions, + Optional<StartingSurface> startingSurface) { super(broadcastDispatcher); mContext = context; mPipOptional = pipOptional; @@ -872,6 +910,7 @@ public class OverviewProxyService extends CurrentUserTracker implements // Connect to the service updateEnabledState(); startConnectionToCurrentUser(); + mStartingSurface = startingSurface; } @Override @@ -938,6 +977,18 @@ public class OverviewProxyService extends CurrentUserTracker implements } } + private void notifyTaskLaunching(int taskId, int supportedType) { + if (mIStartingWindowListener == null) { + return; + } + + try { + mIStartingWindowListener.onTaskLaunching(taskId, supportedType); + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call notifyTaskLaunching()", e); + } + } + private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing) { mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 197582104f8e..bd46ffec759b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -351,7 +351,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_save_message)) + .setContentTitle(getResources().getString(R.string.screenrecord_save_title)) + .setContentText(getResources().getString(R.string.screenrecord_save_text)) .setContentIntent(PendingIntent.getActivity( this, REQUEST_CODE, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index 1386ddfa7692..bb8c36776d57 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -65,6 +65,9 @@ public class CropView extends View { private float mTopDelta = 0f; private float mBottomDelta = 0f; + private int mExtraTopPadding; + private int mExtraBottomPadding; + private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE; private float mStartingY; // y coordinate of ACTION_DOWN private CropInteractionListener mCropInteractionListener; @@ -97,8 +100,8 @@ public class CropView extends View { float bottom = mBottomCrop + mBottomDelta; drawShade(canvas, 0, top); drawShade(canvas, bottom, 1f); - drawHandle(canvas, top); - drawHandle(canvas, bottom); + drawHandle(canvas, top, /* draw the handle tab down */ false); + drawHandle(canvas, bottom, /* draw the handle tab up */ true); } @Override @@ -117,12 +120,13 @@ public class CropView extends View { if (mCurrentDraggingBoundary != CropBoundary.NONE) { float delta = event.getY() - mStartingY; if (mCurrentDraggingBoundary == CropBoundary.TOP) { - mTopDelta = pixelsToFraction((int) MathUtils.constrain(delta, -topPx, + mTopDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta, + -topPx + mExtraTopPadding, bottomPx - 2 * mCropTouchMargin - topPx)); } else { // Bottom - mBottomDelta = pixelsToFraction((int) MathUtils.constrain(delta, + mBottomDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta, topPx + 2 * mCropTouchMargin - bottomPx, - getMeasuredHeight() - bottomPx)); + getHeight() - bottomPx - mExtraBottomPadding)); } updateListener(event); invalidate(); @@ -132,7 +136,7 @@ public class CropView extends View { case MotionEvent.ACTION_UP: if (mCurrentDraggingBoundary != CropBoundary.NONE) { // Commit the delta to the stored crop values. - commitDeltas(); + commitDeltas(mCurrentDraggingBoundary); updateListener(event); } } @@ -140,6 +144,25 @@ public class CropView extends View { } /** + * Set the given boundary to the given value without animation. + */ + public void setBoundaryTo(CropBoundary boundary, float value) { + switch (boundary) { + case TOP: + mTopCrop = value; + break; + case BOTTOM: + mBottomCrop = value; + break; + case NONE: + Log.w(TAG, "No boundary selected for animation"); + break; + } + + invalidate(); + } + + /** * Animate the given boundary to the given value. */ public void animateBoundaryTo(CropBoundary boundary, float value) { @@ -161,12 +184,12 @@ public class CropView extends View { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - commitDeltas(); + commitDeltas(boundary); } @Override public void onAnimationCancel(Animator animation) { - commitDeltas(); + commitDeltas(boundary); } }); animator.setFloatValues(0f, 1f); @@ -176,6 +199,16 @@ public class CropView extends View { } /** + * Set additional top and bottom padding for the image being cropped (used when the + * corresponding ImageView doesn't take the full height). + */ + public void setExtraPadding(int top, int bottom) { + mExtraTopPadding = top; + mExtraBottomPadding = bottom; + invalidate(); + } + + /** * @return value [0,1] representing the position of the top crop boundary. Does not reflect * changes from any in-progress touch input. */ @@ -195,11 +228,14 @@ public class CropView extends View { mCropInteractionListener = listener; } - private void commitDeltas() { - mTopCrop += mTopDelta; - mBottomCrop += mBottomDelta; - mTopDelta = 0; - mBottomDelta = 0; + private void commitDeltas(CropBoundary boundary) { + if (boundary == CropBoundary.TOP) { + mTopCrop += mTopDelta; + mTopDelta = 0; + } else if (boundary == CropBoundary.BOTTOM) { + mBottomCrop += mBottomDelta; + mBottomDelta = 0; + } } private void updateListener(MotionEvent event) { @@ -212,21 +248,35 @@ public class CropView extends View { } private void drawShade(Canvas canvas, float fracStart, float fracEnd) { - canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(), + canvas.drawRect(0, fractionToPixels(fracStart), getWidth(), fractionToPixels(fracEnd), mShadePaint); } - private void drawHandle(Canvas canvas, float frac) { + private void drawHandle(Canvas canvas, float frac, boolean handleTabUp) { int y = fractionToPixels(frac); - canvas.drawLine(0, y, getMeasuredWidth(), y, mHandlePaint); + canvas.drawLine(0, y, getWidth(), y, mHandlePaint); + float radius = 15 * getResources().getDisplayMetrics().density; + float x = getWidth() * .9f; + canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabUp ? 180 : 0, 180, + true, mHandlePaint); } + /** + * Convert the given fraction position to pixel position within the View. + */ private int fractionToPixels(float frac) { - return (int) (frac * getMeasuredHeight()); + return (int) (mExtraTopPadding + frac * getImageHeight()); } - private float pixelsToFraction(int px) { - return px / (float) getMeasuredHeight(); + private int getImageHeight() { + return getHeight() - mExtraTopPadding - mExtraBottomPadding; + } + + /** + * Convert the given pixel distance to fraction of the image. + */ + private float pixelDistanceToFraction(int px) { + return px / (float) getImageHeight(); } private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index 8fc2830aa422..bc8adc9dad5b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -41,6 +41,7 @@ import com.google.common.util.concurrent.ListenableFuture; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.time.Duration; @@ -110,6 +111,39 @@ class ImageExporter { } /** + * Stores the given Bitmap to a temp file. + */ + ListenableFuture<File> exportAsTempFile(Executor executor, Bitmap bitmap) { + return CallbackToFutureAdapter.getFuture( + (completer) -> { + executor.execute(() -> { + File cachePath; + try { + cachePath = File.createTempFile("long_screenshot_cache_", ".tmp"); + try (FileOutputStream stream = new FileOutputStream(cachePath)) { + bitmap.compress(mCompressFormat, mQuality, stream); + } catch (IOException e) { + if (cachePath.exists()) { + //noinspection ResultOfMethodCallIgnored + cachePath.delete(); + cachePath = null; + } + completer.setException(e); + } + if (cachePath != null) { + completer.set(cachePath); + } + } catch (IOException e) { + // Failed to create a new file + completer.setException(e); + } + }); + return "Bitmap#compress"; + } + ); + } + + /** * Export the image using the given executor. * * @param executor the thread for execution @@ -122,7 +156,7 @@ class ImageExporter { } /** - * Export the image using the given executor. + * Export the image to MediaStore and publish. * * @param executor the thread for execution * @param bitmap the bitmap to export @@ -131,8 +165,10 @@ class ImageExporter { */ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime) { - final Task task = - new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality); + + final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, + mQuality, /* publish */ true); + return CallbackToFutureAdapter.getFuture( (completer) -> { executor.execute(() -> { @@ -147,12 +183,36 @@ class ImageExporter { ); } + /** + * Delete the entry. + * + * @param executor the thread for execution + * @param uri the uri of the image to publish + * + * @return a listenable future result + */ + ListenableFuture<Result> delete(Executor executor, Uri uri) { + return CallbackToFutureAdapter.getFuture((completer) -> { + executor.execute(() -> { + mResolver.delete(uri, null); + + Result result = new Result(); + result.uri = uri; + result.deleted = true; + completer.set(result); + }); + return "ContentResolver#delete"; + }); + } + static class Result { + Uri uri; UUID requestId; String fileName; long timestamp; - Uri uri; CompressFormat format; + boolean published; + boolean deleted; } private static class Task { @@ -163,9 +223,10 @@ class ImageExporter { private final CompressFormat mFormat; private final int mQuality; private final String mFileName; + private final boolean mPublish; Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, - CompressFormat format, int quality) { + CompressFormat format, int quality, boolean publish) { mResolver = resolver; mRequestId = requestId; mBitmap = bitmap; @@ -173,6 +234,7 @@ class ImageExporter { mFormat = format; mQuality = quality; mFileName = createFilename(mCaptureTime, mFormat); + mPublish = publish; } public Result execute() throws ImageExportException, InterruptedException { @@ -186,16 +248,21 @@ class ImageExporter { start = Instant.now(); } - uri = createEntry(mFormat, mCaptureTime, mFileName); + uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName); throwIfInterrupted(); - writeImage(mBitmap, mFormat, mQuality, uri); + writeImage(mResolver, mBitmap, mFormat, mQuality, uri); throwIfInterrupted(); - writeExif(uri, mRequestId, mBitmap.getWidth(), mBitmap.getHeight(), mCaptureTime); + int width = mBitmap.getWidth(); + int height = mBitmap.getHeight(); + writeExif(mResolver, uri, mRequestId, width, height, mCaptureTime); throwIfInterrupted(); - publishEntry(uri); + if (mPublish) { + publishEntry(mResolver, uri); + result.published = true; + } result.timestamp = mCaptureTime.toInstant().toEpochMilli(); result.requestId = mRequestId; @@ -218,88 +285,89 @@ class ImageExporter { return result; } - Uri createEntry(CompressFormat format, ZonedDateTime time, String fileName) - throws ImageExportException { - Trace.beginSection("ImageExporter_createEntry"); - try { - final ContentValues values = createMetadata(time, format, fileName); + @Override + public String toString() { + return "export [" + mBitmap + "] to [" + mFormat + "] at quality " + mQuality; + } + } - Uri uri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - if (uri == null) { - throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); - } - return uri; - } finally { - Trace.endSection(); + private static Uri createEntry(ContentResolver resolver, CompressFormat format, + ZonedDateTime time, String fileName) throws ImageExportException { + Trace.beginSection("ImageExporter_createEntry"); + try { + final ContentValues values = createMetadata(time, format, fileName); + + Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + if (uri == null) { + throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); } + return uri; + } finally { + Trace.endSection(); } + } - void writeImage(Bitmap bitmap, CompressFormat format, int quality, - Uri contentUri) throws ImageExportException { - Trace.beginSection("ImageExporter_writeImage"); - try (OutputStream out = mResolver.openOutputStream(contentUri)) { - long start = SystemClock.elapsedRealtime(); - if (!bitmap.compress(format, quality, out)) { - throw new ImageExportException(IMAGE_COMPRESS_RETURNED_FALSE); - } else if (LogConfig.DEBUG_STORAGE) { - Log.d(TAG, "Bitmap.compress took " - + (SystemClock.elapsedRealtime() - start) + " ms"); - } - } catch (IOException ex) { - throw new ImageExportException(OPEN_OUTPUT_STREAM_EXCEPTION, ex); - } finally { - Trace.endSection(); + private static void writeImage(ContentResolver resolver, Bitmap bitmap, CompressFormat format, + int quality, Uri contentUri) throws ImageExportException { + Trace.beginSection("ImageExporter_writeImage"); + try (OutputStream out = resolver.openOutputStream(contentUri)) { + long start = SystemClock.elapsedRealtime(); + if (!bitmap.compress(format, quality, out)) { + throw new ImageExportException(IMAGE_COMPRESS_RETURNED_FALSE); + } else if (LogConfig.DEBUG_STORAGE) { + Log.d(TAG, "Bitmap.compress took " + + (SystemClock.elapsedRealtime() - start) + " ms"); } + } catch (IOException ex) { + throw new ImageExportException(OPEN_OUTPUT_STREAM_EXCEPTION, ex); + } finally { + Trace.endSection(); } + } - void writeExif(Uri uri, UUID requestId, int width, int height, ZonedDateTime captureTime) - throws ImageExportException { - Trace.beginSection("ImageExporter_writeExif"); - ParcelFileDescriptor pfd = null; + private static void writeExif(ContentResolver resolver, Uri uri, UUID requestId, int width, + int height, ZonedDateTime captureTime) throws ImageExportException { + Trace.beginSection("ImageExporter_writeExif"); + ParcelFileDescriptor pfd = null; + try { + pfd = resolver.openFile(uri, "rw", null); + if (pfd == null) { + throw new ImageExportException(RESOLVER_OPEN_FILE_RETURNED_NULL); + } + ExifInterface exif; try { - pfd = mResolver.openFile(uri, "rw", null); - if (pfd == null) { - throw new ImageExportException(RESOLVER_OPEN_FILE_RETURNED_NULL); - } - ExifInterface exif; - try { - exif = new ExifInterface(pfd.getFileDescriptor()); - } catch (IOException e) { - throw new ImageExportException(EXIF_READ_EXCEPTION, e); - } - - updateExifAttributes(exif, requestId, width, height, captureTime); - try { - exif.saveAttributes(); - } catch (IOException e) { - throw new ImageExportException(EXIF_WRITE_EXCEPTION, e); - } - } catch (FileNotFoundException e) { - throw new ImageExportException(RESOLVER_OPEN_FILE_EXCEPTION, e); - } finally { - closeQuietly(pfd); - Trace.endSection(); + exif = new ExifInterface(pfd.getFileDescriptor()); + } catch (IOException e) { + throw new ImageExportException(EXIF_READ_EXCEPTION, e); } - } - void publishEntry(Uri uri) throws ImageExportException { - Trace.beginSection("ImageExporter_publishEntry"); + updateExifAttributes(exif, requestId, width, height, captureTime); try { - ContentValues values = new ContentValues(); - values.put(MediaStore.MediaColumns.IS_PENDING, 0); - values.putNull(MediaStore.MediaColumns.DATE_EXPIRES); - final int rowsUpdated = mResolver.update(uri, values, /* extras */ null); - if (rowsUpdated < 1) { - throw new ImageExportException(RESOLVER_UPDATE_ZERO_ROWS); - } - } finally { - Trace.endSection(); + exif.saveAttributes(); + } catch (IOException e) { + throw new ImageExportException(EXIF_WRITE_EXCEPTION, e); } + } catch (FileNotFoundException e) { + throw new ImageExportException(RESOLVER_OPEN_FILE_EXCEPTION, e); + } finally { + closeQuietly(pfd); + Trace.endSection(); } + } - @Override - public String toString() { - return "compress [" + mBitmap + "] to [" + mFormat + "] at quality " + mQuality; + private static void publishEntry(ContentResolver resolver, Uri uri) + throws ImageExportException { + Trace.beginSection("ImageExporter_publishEntry"); + try { + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.IS_PENDING, 0); + values.putNull(MediaStore.MediaColumns.DATE_EXPIRES); + final int rowsUpdated = resolver.update(uri, values, /* extras */ null); + if (rowsUpdated < 1) { + throw new ImageExportException(RESOLVER_UPDATE_ZERO_ROWS); + } + } finally { + Trace.endSection(); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java new file mode 100644 index 000000000000..988b93c8ca59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java @@ -0,0 +1,94 @@ +/* + * 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.screenshot; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.ParcelFileDescriptor; + +import androidx.concurrent.futures.CallbackToFutureAdapter; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.inject.Inject; + +/** Loads images. */ +public class ImageLoader { + private final ContentResolver mResolver; + + static class Result { + @Nullable Uri uri; + @Nullable File fileName; + @Nullable Bitmap bitmap; + } + + @Inject + ImageLoader(ContentResolver resolver) { + mResolver = resolver; + } + + /** + * Loads an image via URI from ContentResolver. + * + * @param uri the identifier of the image to load + * @return a listenable future result + */ + ListenableFuture<Result> load(Uri uri) { + return CallbackToFutureAdapter.getFuture(completer -> { + Result result = new Result(); + try (InputStream in = mResolver.openInputStream(uri)) { + result.uri = uri; + result.bitmap = BitmapFactory.decodeStream(in); + completer.set(result); + } + catch (IOException e) { + completer.setException(e); + } + return "BitmapFactory#decodeStream"; + }); + } + + /** + * Loads an image by physical filesystem name. The current user must have filesystem + * permissions to read this file/path. + * + * @param file the system file path of the image to load + * @return a listenable future result + */ + ListenableFuture<Result> load(File file) { + return CallbackToFutureAdapter.getFuture(completer -> { + try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { + Result result = new Result(); + result.fileName = file; + result.bitmap = BitmapFactory.decodeStream(in); + completer.set(result); + } catch (IOException e) { + completer.setException(e); + } + return "BitmapFactory#decodeStream"; + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java index ae3cd9996f04..6743afa3ab59 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java @@ -15,6 +15,7 @@ */ package com.android.systemui.screenshot; +import android.annotation.AnyThread; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; import android.graphics.RecordingCanvas; @@ -26,6 +27,9 @@ import android.util.Log; import androidx.annotation.UiThread; +import com.android.internal.util.CallbackRegistry; +import com.android.internal.util.CallbackRegistry.NotifierCallback; + import java.util.ArrayList; import java.util.List; @@ -34,10 +38,14 @@ import java.util.List; * <p> * To display on-screen, use {@link #getDrawable()}. */ +@UiThread class ImageTileSet { private static final String TAG = "ImageTileSet"; + private CallbackRegistry<OnBoundsChangedListener, ImageTileSet, Rect> mOnBoundsListeners; + private CallbackRegistry<OnContentChangedListener, ImageTileSet, Rect> mContentListeners; + ImageTileSet(@UiThread Handler handler) { mHandler = handler; } @@ -64,15 +72,43 @@ class ImageTileSet { private OnContentChangedListener mOnContentChangedListener; private OnBoundsChangedListener mOnBoundsChangedListener; - void setOnBoundsChangedListener(OnBoundsChangedListener listener) { - mOnBoundsChangedListener = listener; - } - - void setOnContentChangedListener(OnContentChangedListener listener) { - mOnContentChangedListener = listener; + void addOnBoundsChangedListener(OnBoundsChangedListener listener) { + if (mOnBoundsListeners == null) { + mOnBoundsListeners = new CallbackRegistry<>( + new NotifierCallback<OnBoundsChangedListener, ImageTileSet, Rect>() { + @Override + public void onNotifyCallback(OnBoundsChangedListener callback, + ImageTileSet sender, + int arg, Rect newBounds) { + callback.onBoundsChanged(newBounds.left, newBounds.top, newBounds.right, + newBounds.bottom); + } + }); + } + mOnBoundsListeners.add(listener); + } + + void addOnContentChangedListener(OnContentChangedListener listener) { + if (mContentListeners == null) { + mContentListeners = new CallbackRegistry<>( + new NotifierCallback<OnContentChangedListener, ImageTileSet, Rect>() { + @Override + public void onNotifyCallback(OnContentChangedListener callback, + ImageTileSet sender, + int arg, Rect newBounds) { + callback.onContentChanged(); + } + }); + } + mContentListeners.add(listener); } + @AnyThread void addTile(ImageTile tile) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(() -> addTile(tile)); + return; + } final Rect newBounds = new Rect(mBounds); final Rect newRect = tile.getLocation(); mTiles.add(tile); @@ -84,27 +120,15 @@ class ImageTileSet { notifyContentChanged(); } - void notifyContentChanged() { - if (mOnContentChangedListener == null) { - return; - } - if (mHandler.getLooper().isCurrentThread()) { - mOnContentChangedListener.onContentChanged(); - } else { - mHandler.post(() -> mOnContentChangedListener.onContentChanged()); + private void notifyContentChanged() { + if (mContentListeners != null) { + mContentListeners.notifyCallbacks(this, 0, null); } } - void notifyBoundsChanged(Rect bounds) { - if (mOnBoundsChangedListener == null) { - return; - } - if (mHandler.getLooper().isCurrentThread()) { - mOnBoundsChangedListener.onBoundsChanged( - bounds.left, bounds.top, bounds.right, bounds.bottom); - } else { - mHandler.post(() -> mOnBoundsChangedListener.onBoundsChanged( - bounds.left, bounds.top, bounds.right, bounds.bottom)); + private void notifyBoundsChanged(Rect bounds) { + if (mOnBoundsListeners != null) { + mOnBoundsListeners.notifyCallbacks(this, 0, bounds); } } @@ -180,8 +204,13 @@ class ImageTileSet { return mBounds.height(); } + @AnyThread void clear() { - if (mBounds.isEmpty()) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(this::clear); + return; + } + if (mTiles.isEmpty()) { return; } mBounds.setEmpty(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java new file mode 100644 index 000000000000..db997053af23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -0,0 +1,362 @@ +/* + * 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.screenshot; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.HardwareRenderer; +import android.graphics.RecordingCanvas; +import android.graphics.Rect; +import android.graphics.RenderNode; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.SystemClock; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * LongScreenshotActivity acquires bitmap data for a long screenshot and lets the user trim the top + * and bottom before saving/sharing/editing. + */ +public class LongScreenshotActivity extends Activity { + private static final String TAG = "LongScreenshotActivity"; + + private static final String IMAGE_PATH_KEY = "saved-image"; + private static final String TOP_BOUNDARY_KEY = "top-boundary"; + private static final String BOTTOM_BOUNDARY_KEY = "bottom-boundary"; + + private final UiEventLogger mUiEventLogger; + private final ScrollCaptureController mScrollCaptureController; + private final ScrollCaptureClient.Connection mConnection; + private final Executor mUiExecutor; + private final Executor mBackgroundExecutor; + private final ImageExporter mImageExporter; + + private String mSavedImagePath; + // If true, the activity is re-loading an image from storage, which should either succeed and + // populate the UI or fail and finish the activity. + private boolean mRestoringInstance; + + private ImageView mPreview; + private View mSave; + private View mCancel; + private View mEdit; + private View mShare; + private CropView mCropView; + private MagnifierView mMagnifierView; + + private enum PendingAction { + SHARE, + EDIT, + SAVE + } + + @Inject + public LongScreenshotActivity(UiEventLogger uiEventLogger, + ImageExporter imageExporter, + @Main Executor mainExecutor, + @Background Executor bgExecutor, + Context context) { + mUiEventLogger = uiEventLogger; + mUiExecutor = mainExecutor; + mBackgroundExecutor = bgExecutor; + mImageExporter = imageExporter; + + mScrollCaptureController = new ScrollCaptureController(context, mainExecutor, bgExecutor, + imageExporter); + + mConnection = ScreenshotController.takeScrollCaptureConnection(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.long_screenshot); + + mPreview = findViewById(R.id.preview); + mSave = findViewById(R.id.save); + mCancel = findViewById(R.id.cancel); + mEdit = findViewById(R.id.edit); + mShare = findViewById(R.id.share); + mCropView = findViewById(R.id.crop_view); + mMagnifierView = findViewById(R.id.magnifier); + mCropView.setCropInteractionListener(mMagnifierView); + + mSave.setOnClickListener(this::onClicked); + mCancel.setOnClickListener(this::onClicked); + mEdit.setOnClickListener(this::onClicked); + mShare.setOnClickListener(this::onClicked); + + if (savedInstanceState != null) { + String imagePath = savedInstanceState.getString(IMAGE_PATH_KEY); + if (!TextUtils.isEmpty(imagePath)) { + mRestoringInstance = true; + mBackgroundExecutor.execute(() -> { + Bitmap bitmap = BitmapFactory.decodeFile(imagePath); + if (bitmap == null) { + Log.e(TAG, "Failed to read bitmap from " + imagePath); + finishAndRemoveTask(); + } else { + runOnUiThread(() -> { + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + mPreview.setImageDrawable(drawable); + mMagnifierView.setDrawable(drawable, bitmap.getWidth(), + bitmap.getHeight()); + + mCropView.setBoundaryTo(CropView.CropBoundary.TOP, + savedInstanceState.getFloat(TOP_BOUNDARY_KEY, 0f)); + mCropView.setBoundaryTo(CropView.CropBoundary.BOTTOM, + savedInstanceState.getFloat(BOTTOM_BOUNDARY_KEY, 1f)); + mRestoringInstance = false; + // Reuse the same path for subsequent restoration. + mSavedImagePath = imagePath; + Log.d(TAG, "Loaded bitmap from " + imagePath); + }); + } + }); + } + } + mPreview.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + updateCropLocation()); + } + + @Override + public void onStart() { + super.onStart(); + if (mPreview.getDrawable() == null && !mRestoringInstance) { + if (mConnection == null) { + Log.e(TAG, "Failed to get scroll capture connection, bailing out"); + finishAndRemoveTask(); + return; + } + doCapture(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(IMAGE_PATH_KEY, mSavedImagePath); + outState.putFloat(TOP_BOUNDARY_KEY, mCropView.getTopBoundary()); + outState.putFloat(BOTTOM_BOUNDARY_KEY, mCropView.getBottomBoundary()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (isFinishing() && !TextUtils.isEmpty(mSavedImagePath)) { + Log.d(TAG, "Deleting " + mSavedImagePath); + File file = new File(mSavedImagePath); + file.delete(); + } + } + + private void setButtonsEnabled(boolean enabled) { + mSave.setEnabled(enabled); + mCancel.setEnabled(enabled); + mEdit.setEnabled(enabled); + mShare.setEnabled(enabled); + } + + private void doEdit(Uri uri) { + String editorPackage = getString(R.string.config_screenshotEditor); + Intent intent = new Intent(Intent.ACTION_EDIT); + if (!TextUtils.isEmpty(editorPackage)) { + intent.setComponent(ComponentName.unflattenFromString(editorPackage)); + } + intent.setDataAndType(uri, "image/png"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + startActivityAsUser(intent, UserHandle.CURRENT); + finishAndRemoveTask(); + } + + private void doShare(Uri uri) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("image/png"); + intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_GRANT_READ_URI_PERMISSION); + Intent sharingChooserIntent = Intent.createChooser(intent, null) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT); + } + + private void onClicked(View v) { + int id = v.getId(); + v.setPressed(true); + setButtonsEnabled(false); + if (id == R.id.save) { + startExport(PendingAction.SAVE); + } else if (id == R.id.cancel) { + finishAndRemoveTask(); + } else if (id == R.id.edit) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT); + startExport(PendingAction.EDIT); + } else if (id == R.id.share) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE); + startExport(PendingAction.SHARE); + } + } + + private void startExport(PendingAction action) { + Drawable drawable = mPreview.getDrawable(); + + Rect croppedPortion = new Rect( + 0, + (int) (drawable.getIntrinsicHeight() * mCropView.getTopBoundary()), + drawable.getIntrinsicWidth(), + (int) (drawable.getIntrinsicHeight() * mCropView.getBottomBoundary())); + ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( + mBackgroundExecutor, UUID.randomUUID(), getBitmap(croppedPortion, drawable), + ZonedDateTime.now()); + exportFuture.addListener(() -> { + try { + ImageExporter.Result result = exportFuture.get(); + setButtonsEnabled(true); + switch (action) { + case EDIT: + doEdit(result.uri); + break; + case SHARE: + doShare(result.uri); + break; + case SAVE: + // Nothing more to do + finishAndRemoveTask(); + break; + } + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "failed to export", e); + setButtonsEnabled(true); + } + }, mUiExecutor); + } + + private Bitmap getBitmap(Rect bounds, Drawable drawable) { + final RenderNode output = new RenderNode("Bitmap Export"); + output.setPosition(0, 0, bounds.width(), bounds.height()); + RecordingCanvas canvas = output.beginRecording(); + // Translating the canvas instead of setting drawable bounds since the drawable is still + // used in the preview. + canvas.translate(0, -bounds.top); + drawable.draw(canvas); + output.endRecording(); + return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height()); + } + + private void saveCacheBitmap(ImageTileSet tileSet) { + long startTime = SystemClock.uptimeMillis(); + Bitmap bitmap = tileSet.toBitmap(); + // TODO(b/181562529) Remove this + mPreview.setImageDrawable(tileSet.getDrawable()); + try { + File file = File.createTempFile("long_screenshot", ".png", null); + FileOutputStream stream = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + stream.flush(); + stream.close(); + mSavedImagePath = file.getAbsolutePath(); + Log.d(TAG, "Saved to " + file.getAbsolutePath() + " in " + + (SystemClock.uptimeMillis() - startTime) + "ms"); + } catch (IOException e) { + Log.e(TAG, "Failed to save bitmap", e); + } + } + + private void updateCropLocation() { + Drawable drawable = mPreview.getDrawable(); + if (drawable == null) { + return; + } + + float imageRatio = drawable.getBounds().width() / (float) drawable.getBounds().height(); + float viewRatio = mPreview.getWidth() / (float) mPreview.getHeight(); + + if (imageRatio > viewRatio) { + // Image is full width and height is constrained, compute extra padding to inform + // CropView + float imageHeight = mPreview.getHeight() * viewRatio / imageRatio; + int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2; + mCropView.setExtraPadding(extraPadding, extraPadding); + } else { + // Image is full height + mCropView.setExtraPadding(0, 0); + } + } + + private void doCapture() { + mScrollCaptureController.start(mConnection, + new ScrollCaptureController.ScrollCaptureCallback() { + @Override + public void onError() { + Log.e(TAG, "Error capturing long screenshot!"); + finishAndRemoveTask(); + } + + @Override + public void onComplete(ImageTileSet imageTileSet, int pageSize) { + Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x " + + imageTileSet.getHeight()); + mPreview.setImageDrawable(imageTileSet.getDrawable()); + updateCropLocation(); + mMagnifierView.setDrawable(imageTileSet.getDrawable(), + imageTileSet.getWidth(), imageTileSet.getHeight()); + // Original boundaries go from the image tile set's y=0 to y=pageSize, so + // we animate to that as a starting crop position. + float topFraction = Math.max(0, + -imageTileSet.getTop() / (float) imageTileSet.getHeight()); + float bottomFraction = Math.min(1f, + 1 - (imageTileSet.getBottom() - pageSize) + / (float) imageTileSet.getHeight()); + mCropView.animateBoundaryTo(CropView.CropBoundary.TOP, topFraction); + mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, bottomFraction); + mBackgroundExecutor.execute(() -> saveCacheBitmap(imageTileSet)); + } + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java index f8f1d3ac9a5b..90f304262ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java @@ -16,6 +16,9 @@ package com.android.systemui.screenshot; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -27,6 +30,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.ViewPropertyAnimator; import androidx.annotation.Nullable; @@ -34,7 +38,7 @@ import com.android.systemui.R; /** * MagnifierView shows a full-res cropped circular display of a given ImageTileSet, contents and - * positioning dereived from events from a CropView to which it listens. + * positioning derived from events from a CropView to which it listens. * * Not meant to be a general-purpose magnifier! */ @@ -56,6 +60,20 @@ public class MagnifierView extends View implements CropView.CropInteractionListe private float mLastCropPosition; private CropView.CropBoundary mCropBoundary; + private ViewPropertyAnimator mTranslationAnimator; + private final Animator.AnimatorListener mTranslationAnimatorListener = + new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + mTranslationAnimator = null; + } + + @Override + public void onAnimationEnd(Animator animation) { + mTranslationAnimator = null; + } + }; + public MagnifierView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } @@ -77,13 +95,12 @@ public class MagnifierView extends View implements CropView.CropInteractionListe mCheckerboardPaint.setColor(Color.GRAY); } - public void setImageTileset(ImageTileSet tiles) { - if (tiles != null) { - mDrawable = tiles.getDrawable(); - mDrawable.setBounds(0, 0, tiles.getWidth(), tiles.getHeight()); - } else { - mDrawable = null; - } + /** + * Set the drawable to be displayed by the magnifier. + */ + public void setDrawable(@NonNull Drawable drawable, int width, int height) { + mDrawable = drawable; + mDrawable.setBounds(0, 0, width, height); invalidate(); } @@ -133,6 +150,8 @@ public class MagnifierView extends View implements CropView.CropInteractionListe public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary, float cropPosition, int cropPositionPx) { mCropBoundary = boundary; + boolean touchOnRight = event.getX() > getParentWidth() / 2; + float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastCropPosition = cropPosition; @@ -144,11 +163,22 @@ public class MagnifierView extends View implements CropView.CropInteractionListe setAlpha(0f); setTranslationX((getParentWidth() - getWidth()) / 2); setVisibility(View.VISIBLE); - boolean touchOnRight = event.getX() > getParentWidth() / 2; - float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); - animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f).start(); + mTranslationAnimator = + animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f); + mTranslationAnimator.setListener(mTranslationAnimatorListener); + mTranslationAnimator.start(); break; case MotionEvent.ACTION_MOVE: + // The touch is near the middle if it's within 10% of the center point. + // We don't want to animate horizontally if the touch is near the middle. + boolean nearMiddle = Math.abs(event.getX() - getParentWidth() / 2) + < getParentWidth() / 10f; + boolean viewOnLeft = getTranslationX() < (getParentWidth() - getWidth()) / 2; + if (!nearMiddle && viewOnLeft != touchOnRight && mTranslationAnimator == null) { + mTranslationAnimator = animate().translationX(translateXTarget); + mTranslationAnimator.setListener(mTranslationAnimatorListener); + mTranslationAnimator.start(); + } mLastCropPosition = cropPosition; setTranslationY(cropPositionPx - getHeight() / 2); invalidate(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 953b40b6e17b..3d6dea3cd3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -41,6 +41,7 @@ import android.app.Notification; import android.app.WindowContext; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Insets; @@ -100,6 +101,8 @@ import javax.inject.Inject; public class ScreenshotController { private static final String TAG = logTag(ScreenshotController.class); + private static ScrollCaptureClient.Connection sScrollConnection; + /** * POD used in the AsyncTask which saves an image in the background. */ @@ -219,6 +222,12 @@ public class ScreenshotController { | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); + public static @Nullable ScrollCaptureClient.Connection takeScrollCaptureConnection() { + ScrollCaptureClient.Connection connection = sScrollConnection; + sScrollConnection = null; + return connection; + } + @Inject ScreenshotController( Context context, @@ -316,6 +325,7 @@ public class ScreenshotController { attachWindow(); mWindow.setContentView(mScreenshotView); + mScreenshotView.requestApplyInsets(); mScreenshotView.takePartialScreenshot( rect -> takeScreenshotInternal(finisher, rect)); @@ -509,7 +519,7 @@ public class ScreenshotController { setWindowFocusable(true); if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) { + SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) { View decorView = mWindow.getDecorView(); // Wait until this window is attached to request because it is @@ -597,21 +607,12 @@ public class ScreenshotController { } private void runScrollCapture(ScrollCaptureClient.Connection connection) { - cancelTimeout(); - ScrollCaptureController controller = new ScrollCaptureController(mContext, connection, - mMainExecutor, mBgExecutor, mImageExporter, mUiEventLogger); - controller.attach(mWindow); - controller.start(new TakeScreenshotService.RequestCallback() { - @Override - public void reportError() { - } + sScrollConnection = connection; // For LongScreenshotActivity to pick up. - @Override - public void onFinish() { - Log.d(TAG, "onFinish from ScrollCaptureController"); - finishDismiss(); - } - }); + Intent intent = new Intent(mContext, LongScreenshotActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + dismissScreenshot(false); } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 6cdf6ab5154e..58a54f6ce0ed 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -82,7 +82,7 @@ public class ScreenshotNotificationsController { dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE); if (intent != null) { final PendingIntent pendingIntent = PendingIntent.getActivityAsUser( - mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED, null, UserHandle.CURRENT); + mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); b.setContentIntent(pendingIntent); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java index dc639dce4951..54b99bbe74cc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java @@ -21,24 +21,25 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; import static java.lang.Math.min; import static java.util.Objects.requireNonNull; +import android.annotation.BinderThread; import android.annotation.UiContext; import android.app.ActivityTaskManager; import android.content.Context; import android.graphics.PixelFormat; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.media.Image; import android.media.ImageReader; import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.RemoteException; import android.util.Log; import android.view.IScrollCaptureCallbacks; import android.view.IScrollCaptureConnection; import android.view.IWindowManager; +import android.view.ScrollCaptureResponse; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.view.ScrollCaptureViewSupport; import java.util.function.Consumer; @@ -62,16 +63,19 @@ public class ScrollCaptureClient { */ public interface Connection { /** - * Session start should be deferred until UI is active because of resource allocation and - * potential visible side effects in the target window. - * + * Start a session. + * @param sessionConsumer listener to receive the session once active * @param maxPages the capture buffer size expressed as a multiple of the content height */ + // TODO ListenableFuture void start(Consumer<Session> sessionConsumer, float maxPages); /** - * Close the connection. + * Close the connection. Must end capture if started to avoid potential unwanted visual + * artifacts. + * + * @see Session#end(Runnable) */ void close(); } @@ -119,6 +123,7 @@ public class ScrollCaptureClient { * @param top the top (y) position of the tile to capture, in content rect space * @param consumer listener to be informed of the result */ + // TODO ListenableFuture void requestTile(int top, Consumer<CaptureResult> consumer); /** @@ -129,16 +134,31 @@ public class ScrollCaptureClient { */ int getMaxTiles(); + /** + * @return the height of each image tile + */ int getTileHeight(); + /** + * @return the height of scrollable content being captured + */ int getPageHeight(); + /** + * @return the width of the scrollable page + */ int getPageWidth(); /** + * @return the bounds on screen of the window being captured. + */ + Rect getWindowBounds(); + + /** * End the capture session, return the target app to original state. The listener * will be called when the target app is ready to before visible and interactive. */ + // TODO ListenableFuture void end(Runnable listener); } @@ -185,13 +205,13 @@ public class ScrollCaptureClient { + ", taskId=" + taskId + ", consumer=" + consumer + ")"); } mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId, - new ControllerCallbacks(consumer)); + new ClientCallbacks(consumer)); } catch (RemoteException e) { Log.e(TAG, "Ignored remote exception", e); } } - private static class ControllerCallbacks extends IScrollCaptureCallbacks.Stub implements + private static class ClientCallbacks extends IScrollCaptureCallbacks.Stub implements Connection, Session, IBinder.DeathRecipient { private IScrollCaptureConnection mConnection; @@ -206,46 +226,63 @@ public class ScrollCaptureClient { private int mTileWidth; private Rect mRequestRect; private boolean mStarted; + + private ICancellationSignal mCancellationSignal; + private Rect mWindowBounds; + private Rect mBoundsInWindow; private int mMaxTiles; - private ControllerCallbacks(Consumer<Connection> connectionConsumer) { + private ClientCallbacks(Consumer<Connection> connectionConsumer) { mConnectionConsumer = connectionConsumer; } - // IScrollCaptureCallbacks - + @BinderThread @Override - public void onConnected(IScrollCaptureConnection connection, Rect scrollBounds, - Point positionInWindow) throws RemoteException { + public void onScrollCaptureResponse(ScrollCaptureResponse response) throws RemoteException { if (DEBUG_SCROLL) { - Log.d(TAG, "onConnected(connection=" + connection + ", scrollBounds=" + scrollBounds - + ", positionInWindow=" + positionInWindow + ")"); + Log.d(TAG, "onScrollCaptureResponse(response=" + response + ")"); } - mConnection = connection; - mConnection.asBinder().linkToDeath(this, 0); - mScrollBounds = scrollBounds; - mConnectionConsumer.accept(this); - mConnectionConsumer = null; - - int pxPerPage = mScrollBounds.width() * mScrollBounds.height(); - int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE)); - mTileWidth = mScrollBounds.width(); - mTileHeight = pxPerTile / mScrollBounds.width(); - if (DEBUG_SCROLL) { - Log.d(TAG, "scrollBounds: " + mScrollBounds); - Log.d(TAG, "tile dimen: " + mTileWidth + "x" + mTileHeight); + if (response.isConnected()) { + mConnection = response.getConnection(); + mConnection.asBinder().linkToDeath(this, 0); + mWindowBounds = response.getWindowBounds(); + mBoundsInWindow = response.getBoundsInWindow(); + + int pxPerPage = mBoundsInWindow.width() * mBoundsInWindow.height(); + int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE)); + mTileWidth = mBoundsInWindow.width(); + mTileHeight = pxPerTile / mBoundsInWindow.width(); + if (DEBUG_SCROLL) { + Log.d(TAG, "boundsInWindow: " + mBoundsInWindow); + Log.d(TAG, "tile size: " + mTileWidth + "x" + mTileHeight); + Log.d(TAG, "maxHeight: " + (mMaxTiles * mTileHeight) + "px"); + } + mConnectionConsumer.accept(this); } + mConnectionConsumer = null; } @Override - public void onUnavailable() throws RemoteException { + public void start(Consumer<Session> sessionConsumer, float maxPages) { if (DEBUG_SCROLL) { - Log.d(TAG, "onUnavailable"); + Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + "," + + " maxPages=" + maxPages + ")"); + } + mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE); + mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, + mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); + mSessionConsumer = sessionConsumer; + + try { + mCancellationSignal = mConnection.startCapture(mReader.getSurface()); + mStarted = true; + } catch (RemoteException e) { + Log.w(TAG, "Failed to start", e); + mReader.close(); } - // The targeted app does not support scroll capture - // or the window could not be found... etc etc. } + @BinderThread @Override public void onCaptureStarted() { if (DEBUG_SCROLL) { @@ -256,13 +293,25 @@ public class ScrollCaptureClient { } @Override - public void onCaptureBufferSent(long frameNumber, Rect contentArea) { - Image image = null; - if (frameNumber != ScrollCaptureViewSupport.NO_FRAME_PRODUCED) { - image = mReader.acquireNextImage(); + public void requestTile(int top, Consumer<CaptureResult> consumer) { + if (DEBUG_SCROLL) { + Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")"); + } + cancelPendingRequest(); + mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight); + mResultConsumer = consumer; + try { + mCancellationSignal = mConnection.requestImage(mRequestRect); + } catch (RemoteException e) { + Log.e(TAG, "Caught remote exception from requestImage", e); } + } + + @Override + public void onImageRequestCompleted(int flags, Rect contentArea) { + Image image = mReader.acquireLatestImage(); if (DEBUG_SCROLL) { - Log.d(TAG, "onCaptureBufferSent(frameNumber=" + frameNumber + Log.d(TAG, "onCaptureBufferSent(flags=" + flags + ", contentArea=" + contentArea + ") image=" + image); } // Save and clear first, since the consumer will likely request the next @@ -273,17 +322,49 @@ public class ScrollCaptureClient { } @Override - public void onConnectionClosed() { + public void end(Runnable listener) { if (DEBUG_SCROLL) { - Log.d(TAG, "onConnectionClosed()"); + Log.d(TAG, "end(listener=" + listener + ")"); } - disconnect(); + if (mStarted) { + mShutdownListener = listener; + mReader.close(); + try { + // listener called from onConnectionClosed callback + mConnection.endCapture(); + } catch (RemoteException e) { + Log.d(TAG, "Ignored exception from endCapture()", e); + disconnect(); + listener.run(); + } + } else { + disconnect(); + listener.run(); + } + } + + @BinderThread + @Override + public void onCaptureEnded() { + close(); if (mShutdownListener != null) { mShutdownListener.run(); mShutdownListener = null; } } + @Override + public void close() { + if (mConnection != null) { + try { + mConnection.close(); + } catch (RemoteException e) { + /* ignore */ + } + disconnect(); + } + } + // Misc private void disconnect() { @@ -293,63 +374,25 @@ public class ScrollCaptureClient { mConnection = null; } - // ScrollCaptureController.Connection - - @Override - public void start(Consumer<Session> sessionConsumer, float maxPages) { - if (DEBUG_SCROLL) { - Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + "," - + " maxPages=" + maxPages + ")" - + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]"); - } - mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE); - mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, - mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); - mSessionConsumer = sessionConsumer; - try { - mConnection.startCapture(mReader.getSurface()); - mStarted = true; - } catch (RemoteException e) { - Log.w(TAG, "Failed to start", e); - } - } - - @Override - public void close() { - end(null); - } - - // ScrollCaptureController.Session - + /** + * The process hosting the window went away abruptly! + */ @Override - public void end(Runnable listener) { + public void binderDied() { if (DEBUG_SCROLL) { - Log.d(TAG, "end(listener=" + listener + ")"); - } - if (mStarted) { - mShutdownListener = listener; - try { - // listener called from onConnectionClosed callback - mConnection.endCapture(); - } catch (RemoteException e) { - Log.d(TAG, "Ignored exception from endCapture()", e); - disconnect(); - listener.run(); - } - } else { - disconnect(); - listener.run(); + Log.d(TAG, "binderDied()"); } + disconnect(); } @Override public int getPageHeight() { - return mScrollBounds.height(); + return mBoundsInWindow.height(); } @Override public int getPageWidth() { - return mScrollBounds.width(); + return mBoundsInWindow.width(); } @Override @@ -357,34 +400,24 @@ public class ScrollCaptureClient { return mTileHeight; } - @Override - public int getMaxTiles() { - return mMaxTiles; + public Rect getWindowBounds() { + return new Rect(mWindowBounds); } @Override - public void requestTile(int top, Consumer<CaptureResult> consumer) { - if (DEBUG_SCROLL) { - Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")"); - } - mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight); - mResultConsumer = consumer; - try { - mConnection.requestImage(mRequestRect); - } catch (RemoteException e) { - Log.e(TAG, "Caught remote exception from requestImage", e); - } + public int getMaxTiles() { + return mMaxTiles; } - /** - * The process hosting the window went away abruptly! - */ - @Override - public void binderDied() { - if (DEBUG_SCROLL) { - Log.d(TAG, "binderDied()"); + private void cancelPendingRequest() { + if (mCancellationSignal != null) { + try { + mCancellationSignal.cancel(); + } catch (RemoteException e) { + /* ignore */ + } + mCancellationSignal = null; } - disconnect(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index ad5e637b189e..34094cd81120 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -16,238 +16,80 @@ package com.android.systemui.screenshot; -import android.annotation.IdRes; import android.annotation.UiThread; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.graphics.Rect; import android.net.Uri; -import android.os.UserHandle; import android.provider.Settings; -import android.text.TextUtils; import android.util.Log; -import android.view.View; -import android.view.ViewTreeObserver.InternalInsetsInfo; -import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; -import android.view.Window; -import android.widget.ImageView; -import com.android.internal.logging.UiEventLogger; -import com.android.systemui.R; import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; import com.android.systemui.screenshot.ScrollCaptureClient.Connection; import com.android.systemui.screenshot.ScrollCaptureClient.Session; -import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; - -import com.google.common.util.concurrent.ListenableFuture; import java.time.ZonedDateTime; import java.util.UUID; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; /** * Interaction controller between the UI and ScrollCaptureClient. */ -public class ScrollCaptureController implements OnComputeInternalInsetsListener { +public class ScrollCaptureController { private static final String TAG = "ScrollCaptureController"; private static final float MAX_PAGES_DEFAULT = 3f; private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages"; + // Portion of the tiles to be acquired above the starting position in infinite scroll + // situations. 1.0 means maximize the area above, 0 means just go down. + private static final float IDEAL_PORTION_ABOVE = 0.4f; - private static final int UP = -1; - private static final int DOWN = 1; + private boolean mScrollingUp = true; + // If true, stop acquiring images when no more bitmap data is available in the current direction + // or if the desired bitmap size is reached. + private boolean mFinishOnBoundary; - private int mDirection = DOWN; - private boolean mAtBottomEdge; - private boolean mAtTopEdge; private Session mSession; - // TODO: Support saving without additional action. - private enum PendingAction { - SHARE, - EDIT, - SAVE - } - public static final int MAX_HEIGHT = 12000; - private final Connection mConnection; private final Context mContext; private final Executor mUiExecutor; private final Executor mBgExecutor; private final ImageExporter mImageExporter; private final ImageTileSet mImageTileSet; - private final UiEventLogger mUiEventLogger; private ZonedDateTime mCaptureTime; private UUID mRequestId; - private RequestCallback mCallback; - private Window mWindow; - private ImageView mPreview; - private View mSave; - private View mCancel; - private View mEdit; - private View mShare; - private CropView mCropView; - private MagnifierView mMagnifierView; + private ScrollCaptureCallback mCaptureCallback; - public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor, - Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) { + public ScrollCaptureController(Context context, Executor uiExecutor, Executor bgExecutor, + ImageExporter exporter) { mContext = context; - mConnection = connection; mUiExecutor = uiExecutor; mBgExecutor = bgExecutor; mImageExporter = exporter; - mUiEventLogger = uiEventLogger; mImageTileSet = new ImageTileSet(context.getMainThreadHandler()); } /** - * @param window the window to display the preview - */ - public void attach(Window window) { - mWindow = window; - } - - /** * Run scroll capture! * + * @param connection connection to the remote window to be used * @param callback request callback to report back to the service */ - public void start(RequestCallback callback) { + public void start(Connection connection, ScrollCaptureCallback callback) { mCaptureTime = ZonedDateTime.now(); mRequestId = UUID.randomUUID(); - mCallback = callback; - - setContentView(R.layout.long_screenshot); - mWindow.getDecorView().getViewTreeObserver() - .addOnComputeInternalInsetsListener(this); - mPreview = findViewById(R.id.preview); - - mSave = findViewById(R.id.save); - mCancel = findViewById(R.id.cancel); - mEdit = findViewById(R.id.edit); - mShare = findViewById(R.id.share); - mCropView = findViewById(R.id.crop_view); - mMagnifierView = findViewById(R.id.magnifier); - mCropView.setCropInteractionListener(mMagnifierView); - - mSave.setOnClickListener(this::onClicked); - mCancel.setOnClickListener(this::onClicked); - mEdit.setOnClickListener(this::onClicked); - mShare.setOnClickListener(this::onClicked); + mCaptureCallback = callback; float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(), SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT); - mConnection.start(this::startCapture, maxPages); - } - - - /** Ensure the entire window is touchable */ - public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { - inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + connection.start(this::startCapture, maxPages); } - void disableButtons() { - mSave.setEnabled(false); - mCancel.setEnabled(false); - mEdit.setEnabled(false); - mShare.setEnabled(false); - } - - private void onClicked(View v) { - Log.d(TAG, "button clicked!"); - - int id = v.getId(); - v.setPressed(true); - disableButtons(); - if (id == R.id.save) { - startExport(PendingAction.SAVE); - } else if (id == R.id.cancel) { - doFinish(); - } else if (id == R.id.edit) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT); - startExport(PendingAction.EDIT); - } else if (id == R.id.share) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE); - startExport(PendingAction.SHARE); - } - } - - private void doFinish() { - mPreview.setImageDrawable(null); - mMagnifierView.setImageTileset(null); - mImageTileSet.clear(); - mCallback.onFinish(); - mWindow.getDecorView().getViewTreeObserver() - .removeOnComputeInternalInsetsListener(this); - } - - private void startExport(PendingAction action) { - Rect croppedPortion = new Rect( - 0, - (int) (mImageTileSet.getHeight() * mCropView.getTopBoundary()), - mImageTileSet.getWidth(), - (int) (mImageTileSet.getHeight() * mCropView.getBottomBoundary())); - ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( - mBgExecutor, mRequestId, mImageTileSet.toBitmap(croppedPortion), mCaptureTime); - exportFuture.addListener(() -> { - try { - ImageExporter.Result result = exportFuture.get(); - if (action == PendingAction.EDIT) { - doEdit(result.uri); - } else if (action == PendingAction.SHARE) { - doShare(result.uri); - } - doFinish(); - } catch (InterruptedException | ExecutionException e) { - Log.e(TAG, "failed to export", e); - mCallback.onFinish(); - } - }, mUiExecutor); - } - - private void doEdit(Uri uri) { - String editorPackage = mContext.getString(R.string.config_screenshotEditor); - Intent intent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - intent.setComponent(ComponentName.unflattenFromString(editorPackage)); - } - intent.setType("image/png"); - intent.setData(uri); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - } - - private void doShare(Uri uri) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("image/png"); - intent.setData(uri); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_GRANT_READ_URI_PERMISSION); - Intent sharingChooserIntent = Intent.createChooser(intent, null) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_GRANT_READ_URI_PERMISSION); - - mContext.startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT); - } - - private void setContentView(@IdRes int id) { - mWindow.setContentView(id); - } - - <T extends View> T findViewById(@IdRes int res) { - return mWindow.findViewById(res); - } - - private void onCaptureResult(CaptureResult result) { - Log.d(TAG, "onCaptureResult: " + result); + Log.d(TAG, "onCaptureResult: " + result + " scrolling " + (mScrollingUp ? "UP" : "DOWN") + + " finish on boundary: " + mFinishOnBoundary); boolean emptyResult = result.captured.height() == 0; boolean partialResult = !emptyResult && result.captured.height() < result.requested.height(); @@ -255,34 +97,35 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener if (partialResult || emptyResult) { // Potentially reached a vertical boundary. Extend in the other direction. - switch (mDirection) { - case DOWN: - Log.d(TAG, "Reached bottom edge."); - mAtBottomEdge = true; - mDirection = UP; - break; - case UP: - Log.d(TAG, "Reached top edge."); - mAtTopEdge = true; - mDirection = DOWN; - break; + if (mFinishOnBoundary) { + Log.d(TAG, "Partial/empty: finished!"); + finish = true; + } else { + // We hit a boundary, clear the tiles, capture everything in the opposite direction, + // then finish. + mImageTileSet.clear(); + mFinishOnBoundary = true; + mScrollingUp = !mScrollingUp; + Log.d(TAG, "Partial/empty: cleared, switch direction to finish"); } - - if (mAtTopEdge && mAtBottomEdge) { - Log.d(TAG, "Reached both top and bottom edge, ending."); + } else { + // Got the full requested result, but may have got enough bitmap data now + int expectedTiles = mImageTileSet.size() + 1; + if (expectedTiles >= mSession.getMaxTiles()) { + Log.d(TAG, "Hit max tiles: finished"); + // If we ever hit the max tiles, we've got enough bitmap data to finish (even if we + // weren't sure we'd finish on this pass). finish = true; } else { - // only reverse if the edge was relatively close to the starting point - if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) { - Log.d(TAG, "Restarting in reverse direction."); - - // Because of temporary limitations, we cannot just jump to the opposite edge - // and continue there. Instead, clear the results and start over capturing from - // here in the other direction. - mImageTileSet.clear(); - } else { - Log.d(TAG, "Capture is tall enough, stopping here."); - finish = true; + if (mScrollingUp && !mFinishOnBoundary) { + // During the initial scroll up, we only want to acquire the portion described + // by IDEAL_PORTION_ABOVE. + if (expectedTiles >= mSession.getMaxTiles() * IDEAL_PORTION_ABOVE) { + Log.d(TAG, "Hit ideal portion above: clear and switch direction"); + // We got enough above the start point, now see how far down it can go. + mImageTileSet.clear(); + mScrollingUp = false; + } } } } @@ -297,9 +140,8 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener // Stop when "too tall" - if (mImageTileSet.size() >= mSession.getMaxTiles() - || mImageTileSet.getHeight() > MAX_HEIGHT) { - Log.d(TAG, "Max height and/or tile count reached."); + if (mImageTileSet.getHeight() > MAX_HEIGHT) { + Log.d(TAG, "Max height reached."); finish = true; } @@ -311,8 +153,8 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener return; } - int nextTop = (mDirection == DOWN) ? result.captured.bottom - : result.captured.top - mSession.getTileHeight(); + int nextTop = (mScrollingUp) + ? result.captured.top - mSession.getTileHeight() : result.captured.bottom; Log.d(TAG, "requestTile: " + nextTop); mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult); } @@ -327,11 +169,26 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener Log.d(TAG, "afterCaptureComplete"); if (mImageTileSet.isEmpty()) { - session.end(mCallback::onFinish); + mCaptureCallback.onError(); } else { - mPreview.setImageDrawable(mImageTileSet.getDrawable()); - mMagnifierView.setImageTileset(mImageTileSet); - mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f); + mCaptureCallback.onComplete(mImageTileSet, session.getPageHeight()); } } + + /** + * Callback for image capture completion or error. + */ + public interface ScrollCaptureCallback { + void onComplete(ImageTileSet imageTileSet, int pageHeight); + void onError(); + } + + /** + * Callback for image export completion or error. + */ + public interface ExportCallback { + void onExportComplete(Uri outputUri); + void onError(); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java index 4ec8eb22c67a..71df369aa7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java @@ -38,9 +38,10 @@ public class TiledImageDrawable extends Drawable { public TiledImageDrawable(ImageTileSet tiles) { mTiles = tiles; - mTiles.setOnContentChangedListener(this::onContentChanged); + mTiles.addOnContentChangedListener(this::onContentChanged); } + private void onContentChanged() { if (mNode != null && mNode.hasDisplayList()) { mNode.discardDisplayList(); diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index 9f182e19efaf..658613796498 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -25,8 +25,8 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_SENSOR -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE import android.os.Bundle import android.os.Handler import android.text.Html @@ -81,7 +81,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene dismiss() } } - if (!sensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)) { + if (!sensorPrivacyManager.isSensorPrivacyEnabled(sensor)) { finish() return } @@ -89,9 +89,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene mAlertParams.apply { try { mMessage = Html.fromHtml(getString(when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> R.string.sensor_privacy_start_use_mic_dialog_content - INDIVIDUAL_SENSOR_CAMERA -> + CAMERA -> R.string.sensor_privacy_start_use_camera_dialog_content else -> Resources.ID_NULL }, packageManager.getApplicationInfo(sensorUsePackageName, 0) @@ -102,9 +102,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } mIconId = when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> com.android.internal.R.drawable.perm_group_microphone - INDIVIDUAL_SENSOR_CAMERA -> com.android.internal.R.drawable.perm_group_camera + CAMERA -> com.android.internal.R.drawable.perm_group_camera else -> Resources.ID_NULL } @@ -121,7 +121,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene override fun onStart() { super.onStart() - sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true) + sensorPrivacyManager.suppressSensorPrivacyReminders(sensorUsePackageName, true) unsuppressImmediately = false } @@ -156,11 +156,11 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene if (unsuppressImmediately) { sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) } else { Handler(mainLooper).postDelayed({ sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) } } @@ -170,7 +170,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } private fun disableSensorPrivacy() { - sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false) + sensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, false) unsuppressImmediately = true setResult(RESULT_OK) } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 0bfc8e5d554b..fea521f15b84 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -72,8 +72,6 @@ public class BrightnessController implements ToggleSlider.Listener { private static final Uri BRIGHTNESS_FOR_VR_FLOAT_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT); - private final float mMinimumBacklight; - private final float mMaximumBacklight; private final float mDefaultBacklight; private final float mMinimumBacklightForVr; private final float mMaximumBacklightForVr; @@ -314,10 +312,6 @@ public class BrightnessController implements ToggleSlider.Listener { mDisplayId = mContext.getDisplayId(); PowerManager pm = context.getSystemService(PowerManager.class); - mMinimumBacklight = pm.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - mMaximumBacklight = pm.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); mDefaultBacklight = mContext.getDisplay().getBrightnessDefault(); mMinimumBacklightForVr = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR); @@ -375,8 +369,8 @@ public class BrightnessController implements ToggleSlider.Listener { metric = mAutomatic ? MetricsEvent.ACTION_BRIGHTNESS_AUTO : MetricsEvent.ACTION_BRIGHTNESS; - minBacklight = mMinimumBacklight; - maxBacklight = mMaximumBacklight; + minBacklight = PowerManager.BRIGHTNESS_MIN; + maxBacklight = PowerManager.BRIGHTNESS_MAX; settingToChange = Settings.System.SCREEN_BRIGHTNESS_FLOAT; } final float valFloat = MathUtils.min(convertGammaToLinearFloat(value, @@ -439,8 +433,8 @@ public class BrightnessController implements ToggleSlider.Listener { min = mMinimumBacklightForVr; max = mMaximumBacklightForVr; } else { - min = mMinimumBacklight; - max = mMaximumBacklight; + min = PowerManager.BRIGHTNESS_MIN; + max = PowerManager.BRIGHTNESS_MAX; } // convertGammaToLinearFloat returns 0-1 if (BrightnessSynchronizer.floatEquals(brightnessValue, diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java index a6aec3b7b1b7..0b40e225fb2b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java @@ -17,8 +17,6 @@ package com.android.systemui.settings.brightness; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -29,10 +27,8 @@ import android.widget.SeekBar; import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.statusbar.policy.BrightnessMirrorController; -import com.android.systemui.util.RoundedCornerProgressDrawable; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -274,9 +270,6 @@ public class BrightnessSlider private BrightnessSlider fromTree(ViewGroup root, boolean useMirror) { BrightnessSliderView v = root.requireViewById(R.id.brightness_slider); - // TODO(175026098) Workaround. Remove when b/175026098 is fixed - applyTheme(v); - return new BrightnessSlider(root, v, useMirror); } @@ -286,32 +279,5 @@ public class BrightnessSlider ? R.layout.quick_settings_brightness_dialog_thick : R.layout.quick_settings_brightness_dialog; } - - private LayerDrawable findProgressClippableDrawable(BrightnessSliderView v) { - SeekBar b = v.requireViewById(R.id.slider); - if (b.getProgressDrawable() instanceof LayerDrawable) { - Drawable progress = ((LayerDrawable) b.getProgressDrawable()) - .findDrawableByLayerId(com.android.internal.R.id.progress); - if (progress instanceof RoundedCornerProgressDrawable) { - Drawable inner = ((RoundedCornerProgressDrawable) progress).getDrawable(); - if (inner instanceof LayerDrawable) { - return (LayerDrawable) inner; - } - } - } - return null; - } - - private void applyTheme(BrightnessSliderView v) { - LayerDrawable layer = findProgressClippableDrawable(v); - if (layer != null) { - layer.findDrawableByLayerId(R.id.slider_foreground).setTintList( - Utils.getColorAttr(v.getContext(), - com.android.internal.R.attr.colorControlActivated)); - layer.findDrawableByLayerId(R.id.slider_icon).setTintList( - Utils.getColorAttr(v.getContext(), - com.android.internal.R.attr.colorBackground)); - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 862c27907e0f..c8e0d60ce304 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -82,4 +82,16 @@ public class FeatureFlags { public boolean isMonetEnabled() { return mFlagReader.isEnabled(R.bool.flag_monet); } + + public boolean isNavigationBarOverlayEnabled() { + return mFlagReader.isEnabled(R.bool.flag_navigation_bar_overlay); + } + + public boolean isPMLiteEnabled() { + return mFlagReader.isEnabled(R.bool.flag_pm_lite); + } + + public boolean isAlarmTileAvailable() { + return mFlagReader.isEnabled(R.bool.flag_alarm_tile); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7e2d27ac728a..a4e97a1dc6d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -41,6 +42,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; @@ -277,10 +279,7 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal // avoid calling this method since it has an IPC if (whitelistIpcs(this::isOrganizationOwnedDevice)) { final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); - final CharSequence disclosure = organizationName != null - ? mContext.getResources().getString(R.string.do_disclosure_with_name, - organizationName) - : mContext.getResources().getText(R.string.do_disclosure_generic); + final CharSequence disclosure = getDisclosureText(organizationName); mRotateTextViewController.updateIndication( INDICATION_TYPE_DISCLOSURE, new KeyguardIndication.Builder() @@ -297,6 +296,22 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal } } + private CharSequence getDisclosureText(@Nullable CharSequence organizationName) { + final Resources packageResources = mContext.getResources(); + if (organizationName == null) { + return packageResources.getText(R.string.do_disclosure_generic); + } else if (mDevicePolicyManager.isDeviceManaged() + && mDevicePolicyManager.getDeviceOwnerType( + mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()) + == DEVICE_OWNER_TYPE_FINANCED) { + return packageResources.getString(R.string.do_financed_disclosure_with_name, + organizationName); + } else { + return packageResources.getString(R.string.do_disclosure_with_name, + organizationName); + } + } + private void updateOwnerInfo() { if (!isKeyguardLayoutEnabled()) { mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java index 9ef304d7e83c..992015320fa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java @@ -430,7 +430,7 @@ public class NotificationGroupingUtil { public static final int[] MARGIN_ADJUSTED_VIEWS = { R.id.notification_headerless_view_column, - R.id.line1, + R.id.title, R.id.notification_main_column, R.id.notification_header}; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 20efa32d63c6..e1199077efb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar; -import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN_REVERSE; -import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE; - import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -34,7 +31,6 @@ import android.view.WindowInsets; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -55,8 +51,6 @@ import com.android.systemui.statusbar.phone.NotificationIconContainer; public class NotificationShelf extends ActivatableNotificationView implements View.OnLayoutChangeListener, StateListener { - private static final boolean USE_ANIMATIONS_WHEN_OPENING = - SystemProperties.getBoolean("debug.icon_opening_animations", true); private static final boolean ICON_ANMATIONS_WHILE_SCROLLING = SystemProperties.getBoolean("debug.icon_scroll_animations", true); private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag; @@ -174,18 +168,9 @@ public class NotificationShelf extends ActivatableNotificationView implements float viewEnd = lastViewState.yTranslation + lastViewState.height; viewState.copyFrom(lastViewState); viewState.height = getIntrinsicHeight(); - viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height, getFullyClosedTranslation()); viewState.zTranslation = ambientState.getBaseZHeight(); - // For the small display size, it's not enough to make the icon not covered by - // the top cutout so the denominator add the height of cutout. - // Totally, (getIntrinsicHeight() * 2 + mCutoutHeight) should be smaller then - // mAmbientState.getTopPadding(). - float openedAmount = (viewState.yTranslation - getFullyClosedTranslation()) - / (getIntrinsicHeight() * 2 + mCutoutHeight); - openedAmount = Math.min(1.0f, openedAmount); - viewState.openedAmount = openedAmount; viewState.clipTopAmount = 0; viewState.alpha = 1; viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0; @@ -220,11 +205,6 @@ public class NotificationShelf extends ActivatableNotificationView implements View lastChild = mAmbientState.getLastVisibleBackgroundChild(); mNotGoneIndex = -1; float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; - float expandAmount = 0.0f; - if (shelfStart >= interpolationStart) { - expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight(); - expandAmount = Math.min(1.0f, expandAmount); - } // find the first view that doesn't overlap with the shelf int notGoneIndex = 0; int colorOfViewBeforeLast = NO_COLOR; @@ -239,7 +219,6 @@ public class NotificationShelf extends ActivatableNotificationView implements boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold || (mAmbientState.isExpansionChanging() && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold); - boolean scrolling = currentScrollVelocity > 0; boolean expandingAnimated = mAmbientState.isExpansionChanging() && !mAmbientState.isPanelTracking(); int baseZHeight = mAmbientState.getBaseZHeight(); @@ -249,8 +228,7 @@ public class NotificationShelf extends ActivatableNotificationView implements ActivatableNotificationView previousAnv = null; for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { - ExpandableView child = (ExpandableView) mHostLayoutController.getChildAt(i); - + ExpandableView child = mHostLayoutController.getChildAt(i); if (!child.needsClippingToShelf() || child.getVisibility() == GONE) { continue; } @@ -268,9 +246,8 @@ public class NotificationShelf extends ActivatableNotificationView implements int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex); clipTopAmount = Math.max(clipTop, clipTopAmount); - - float inShelfAmount = updateShelfTransformation(child, expandAmount, scrolling, - scrollingFast, expandingAnimated, isLastChild); + final float inShelfAmount = updateShelfTransformation(child, scrollingFast, + expandingAnimated, isLastChild); // If the current row is an ExpandableNotificationRow, update its color, roundedness, // and icon state. if (child instanceof ExpandableNotificationRow) { @@ -498,96 +475,58 @@ public class NotificationShelf extends ActivatableNotificationView implements /** * @return the amount how much this notification is in the shelf */ - private float updateShelfTransformation(ExpandableView view, float expandAmount, - boolean scrolling, boolean scrollingFast, boolean expandingAnimated, - boolean isLastChild) { - StatusBarIconView icon = view.getShelfIcon(); - NotificationIconContainer.IconState iconState = getIconState(icon); + private float updateShelfTransformation(ExpandableView view, boolean scrollingFast, + boolean expandingAnimated, boolean isLastChild) { // Let calculate how much the view is in the shelf float viewStart = view.getTranslationY(); int fullHeight = view.getActualHeight() + mPaddingBetweenElements; float iconTransformStart = calculateIconTransformationStart(view); - float transformDistance = getIntrinsicHeight() * 1.5f; - transformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount); - transformDistance = Math.min(transformDistance, fullHeight); - // Let's make sure the transform distance is // at most to the icon (relevant for conversations) - transformDistance = Math.min(viewStart + fullHeight - iconTransformStart, - transformDistance); + float transformDistance = Math.min( + viewStart + fullHeight - iconTransformStart, + getIntrinsicHeight()); if (isLastChild) { fullHeight = Math.min(fullHeight, view.getMinHeight() - getIntrinsicHeight()); - transformDistance = Math.min(transformDistance, view.getMinHeight() - - getIntrinsicHeight()); + transformDistance = Math.min( + transformDistance, + view.getMinHeight() - getIntrinsicHeight()); } float viewEnd = viewStart + fullHeight; - handleCustomTransformHeight(view, expandingAnimated, iconState); - - float fullTransitionAmount; - float transitionAmount; - float contentTransformationAmount; + float fullTransitionAmount = 0.0f; + float iconTransitionAmount = 0.0f; float shelfStart = getTranslationY(); - boolean fullyInOrOut = true; - if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) + + if (viewEnd >= shelfStart + && (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) && (mAmbientState.isShadeExpanded() || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { - if (viewStart < shelfStart) { - if (iconState != null && iconState.hasCustomTransformHeight()) { - fullHeight = iconState.customTransformHeight; - transformDistance = iconState.customTransformHeight; - } + if (viewStart < shelfStart) { float fullAmount = (shelfStart - viewStart) / fullHeight; fullAmount = Math.min(1.0f, fullAmount); - float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation( - fullAmount); - interpolatedAmount = NotificationUtils.interpolate( - interpolatedAmount, fullAmount, expandAmount); - fullTransitionAmount = 1.0f - interpolatedAmount; - + fullTransitionAmount = 1.0f - fullAmount; if (isLastChild) { - // If it's the last child we should use all of the notification to transform - // instead of just to the icon, since that can be quite low. - transitionAmount = (shelfStart - viewStart) / transformDistance; + // Reduce icon transform distance to completely fade in shelf icon + // by the time the notification icon fades out, and vice versa + iconTransitionAmount = (shelfStart - viewStart) + / (iconTransformStart - viewStart); } else { - transitionAmount = (shelfStart - iconTransformStart) / transformDistance; + iconTransitionAmount = (shelfStart - iconTransformStart) / transformDistance; } - transitionAmount = MathUtils.constrain(transitionAmount, 0.0f, 1.0f); - transitionAmount = 1.0f - transitionAmount; - fullyInOrOut = false; + iconTransitionAmount = MathUtils.constrain(iconTransitionAmount, 0.0f, 1.0f); + iconTransitionAmount = 1.0f - iconTransitionAmount; } else { + // Fully in shelf. fullTransitionAmount = 1.0f; - transitionAmount = 1.0f; + iconTransitionAmount = 1.0f; } - - // Transforming the content - contentTransformationAmount = (shelfStart - viewStart) / transformDistance; - contentTransformationAmount = Math.min(1.0f, contentTransformationAmount); - contentTransformationAmount = 1.0f - contentTransformationAmount; - } else { - fullTransitionAmount = 0.0f; - transitionAmount = 0.0f; - contentTransformationAmount = 0.0f; - } - if (iconState != null && fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) { - iconState.isLastExpandIcon = false; - iconState.customTransformHeight = NO_VALUE; } - - // Update the content transformation amount - if (view.isAboveShelf() || view.showingPulsing() - || (!isLastChild && iconState != null && !iconState.translateContent)) { - contentTransformationAmount = 0.0f; - } - view.setContentTransformationAmount(contentTransformationAmount, isLastChild); - - // Update the positioning of the icon - updateIconPositioning(view, transitionAmount, fullTransitionAmount, - transformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild); - + updateIconPositioning(view, iconTransitionAmount, + scrollingFast, expandingAnimated, isLastChild); return fullTransitionAmount; } @@ -606,91 +545,33 @@ public class NotificationShelf extends ActivatableNotificationView implements return start; } - private void handleCustomTransformHeight(ExpandableView view, boolean expandingAnimated, - NotificationIconContainer.IconState iconState) { - if (iconState != null && expandingAnimated && mAmbientState.getScrollY() == 0 - && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) { - // We are expanding animated. Because we switch to a linear interpolation in this case, - // the last icon may be stuck in between the shelf position and the notification - // position, which looks pretty bad. We therefore optimize this case by applying a - // shorter transition such that the icon is either fully in the notification or we clamp - // it into the shelf if it's close enough. - // We need to persist this, since after the expansion, the behavior should still be the - // same. - float position = mAmbientState.getIntrinsicPadding() - + mHostLayoutController.getPositionInLinearLayout(view); - int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight(); - if (position < maxShelfStart && position + view.getIntrinsicHeight() >= maxShelfStart - && view.getTranslationY() < position) { - iconState.isLastExpandIcon = true; - iconState.customTransformHeight = NO_VALUE; - // Let's check if we're close enough to snap into the shelf - boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position - < getIntrinsicHeight(); - if (!forceInShelf) { - // We are overlapping the shelf but not enough, so the icon needs to be - // repositioned - iconState.customTransformHeight = (int) (mMaxLayoutHeight - - getIntrinsicHeight() - position); - } - } - } - } - private void updateIconPositioning(ExpandableView view, float iconTransitionAmount, - float fullTransitionAmount, float iconTransformDistance, boolean scrolling, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { StatusBarIconView icon = view.getShelfIcon(); NotificationIconContainer.IconState iconState = getIconState(icon); if (iconState == null) { return; } - boolean forceInShelf = - iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight(); boolean clampInShelf = iconTransitionAmount > 0.5f || isTargetClipped(view); float clampedAmount = clampInShelf ? 1.0f : 0.0f; if (iconTransitionAmount == clampedAmount) { - iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf; - iconState.useFullTransitionAmount = iconState.noAnimations - || (!ICON_ANMATIONS_WHILE_SCROLLING && iconTransitionAmount == 0.0f - && scrolling); - iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING - && iconTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging(); - iconState.translateContent = mMaxLayoutHeight - getTranslationY() - - getIntrinsicHeight() > 0; + iconState.noAnimations = (scrollingFast || expandingAnimated) && !isLastChild; } - if (!forceInShelf && (scrollingFast || (expandingAnimated - && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) { + if (!isLastChild + && (scrollingFast || (expandingAnimated && !ViewState.isAnimatingY(icon)))) { iconState.cancelAnimations(icon); - iconState.useFullTransitionAmount = true; iconState.noAnimations = true; } - if (iconState.hasCustomTransformHeight()) { - iconState.useFullTransitionAmount = true; - } - if (iconState.isLastExpandIcon) { - iconState.translateContent = false; - } float transitionAmount; if (mAmbientState.isHiddenAtAll() && !view.isInShelf()) { transitionAmount = mAmbientState.isFullyHidden() ? 1 : 0; - } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING - || iconState.useFullTransitionAmount - || iconState.useLinearTransitionAmount) { - transitionAmount = iconTransitionAmount; } else { - // We take the clamped position instead - transitionAmount = clampedAmount; + transitionAmount = iconTransitionAmount; iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount && !mNoAnimationsInThisFrame; } - iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING - || iconState.useFullTransitionAmount - ? fullTransitionAmount - : transitionAmount; iconState.clampedAppearAmount = clampedAmount; - setIconTransformationAmount(view, transitionAmount, iconTransformDistance, - clampedAmount != transitionAmount, isLastChild); + setIconTransformationAmount(view, transitionAmount, isLastChild); } private boolean isTargetClipped(ExpandableView view) { @@ -707,111 +588,49 @@ public class NotificationShelf extends ActivatableNotificationView implements return endOfTarget >= getTranslationY() - mPaddingBetweenElements; } - private void setIconTransformationAmount(ExpandableView view, - float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation, + private void setIconTransformationAmount(ExpandableView view, float transitionAmount, boolean isLastChild) { if (!(view instanceof ExpandableNotificationRow)) { return; } ExpandableNotificationRow row = (ExpandableNotificationRow) view; - StatusBarIconView icon = row.getShelfIcon(); NotificationIconContainer.IconState iconState = getIconState(icon); - View rowIcon = row.getShelfTransformationTarget(); - - // Let's resolve the relative positions of the icons - float notificationIconSize = 0.0f; - int iconTopPadding; - int iconStartPadding; - if (rowIcon != null) { - iconTopPadding = row.getRelativeTopPadding(rowIcon); - iconStartPadding = row.getRelativeStartPadding(rowIcon); - notificationIconSize = rowIcon.getHeight(); - } else { - iconTopPadding = mIconAppearTopPadding; - iconStartPadding = 0; + if (iconState == null) { + return; } + iconState.alpha = transitionAmount; - float shelfIconSize = mAmbientState.isFullyHidden() ? mHiddenShelfIconSize : mIconSize; - shelfIconSize = shelfIconSize * icon.getIconScale(); - - // Get the icon correctly positioned in Y - float notificationIconPositionY = row.getTranslationY() + row.getContentTranslation(); - float targetYPosition = 0; - boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf(); - if (usingLinearInterpolation && !stayingInShelf) { - // If we interpolate from the notification position, this might lead to a slightly - // odd interpolation, since the notification position changes as well. - // Let's instead interpolate directly to the top left of the notification - targetYPosition = NotificationUtils.interpolate( - Math.min(notificationIconPositionY + mIconAppearTopPadding - - getTranslationY(), 0), - 0, - transitionAmount); + boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); + iconState.hidden = isAppearing + || (view instanceof ExpandableNotificationRow + && ((ExpandableNotificationRow) view).isLowPriority() + && mShelfIcons.hasMaxNumDot()) + || (transitionAmount == 0.0f && !iconState.isAnimating(icon)) + || row.isAboveShelf() + || row.showingPulsing() + || (!row.isInShelf() && isLastChild) + || row.getTranslationZ() > mAmbientState.getBaseZHeight(); + iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount; + + // Fade in icons at shelf start + // This is important for conversation icons, which are badged and need x reset + iconState.xTranslation = mShelfIcons.getActualPaddingStart(); + + final boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf(); + if (stayingInShelf) { + iconState.iconAppearAmount = 1.0f; + iconState.alpha = 1.0f; + iconState.hidden = false; } - notificationIconPositionY += iconTopPadding; - float shelfIconPositionY = getTranslationY() + icon.getTop(); - shelfIconPositionY += (icon.getHeight() - shelfIconSize) / 2.0f; - float iconYTranslation = NotificationUtils.interpolate( - notificationIconPositionY - shelfIconPositionY, - targetYPosition, - transitionAmount); - - // Get the icon correctly positioned in X - // Even in RTL it's the left, since we're inverting the location in post - float shelfIconPositionX = icon.getLeft(); - shelfIconPositionX += (1.0f - icon.getIconScale()) * icon.getWidth() / 2.0f; - float iconXTranslation = NotificationUtils.interpolate( - iconStartPadding - shelfIconPositionX, - mShelfIcons.getActualPaddingStart(), - transitionAmount); - - // Let's handle the case that there's no Icon - float alpha = 1.0f; - boolean noIcon = !row.isShowingIcon(); - if (noIcon) { - // The view currently doesn't have an icon, lets transform it in! - alpha = transitionAmount; - notificationIconSize = shelfIconSize / 2.0f; - iconXTranslation = mShelfIcons.getActualPaddingStart(); - } - // The notification size is different from the size in the shelf / statusbar - float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize, - transitionAmount); - if (iconState != null) { - iconState.scaleX = newSize / shelfIconSize; - iconState.scaleY = iconState.scaleX; - iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon); - boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); - if (isAppearing) { - iconState.hidden = true; - iconState.iconAppearAmount = 0.0f; - } - iconState.alpha = alpha; - iconState.yTranslation = iconYTranslation; - iconState.xTranslation = iconXTranslation; - if (stayingInShelf) { - iconState.iconAppearAmount = 1.0f; - iconState.alpha = 1.0f; - iconState.scaleX = 1.0f; - iconState.scaleY = 1.0f; - iconState.hidden = false; - } - if (row.isAboveShelf() - || row.showingPulsing() - || (!row.isInShelf() && (isLastChild && row.areGutsExposed() - || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) { - iconState.hidden = true; - } - int backgroundColor = getBackgroundColorWithoutTint(); - int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor); - if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) { - int iconColor = row.getOriginalIconColor(); - shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor, - iconState.iconAppearAmount); - } - iconState.iconColor = shelfColor; + int backgroundColor = getBackgroundColorWithoutTint(); + int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor); + if (row.isShowingIcon() && shelfColor != StatusBarIconView.NO_COLOR) { + int iconColor = row.getOriginalIconColor(); + shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor, + iconState.iconAppearAmount); } + iconState.iconColor = shelfColor; } private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) { @@ -879,42 +698,6 @@ public class NotificationShelf extends ActivatableNotificationView implements return ret; } - private void setOpenedAmount(float openedAmount) { - mNoAnimationsInThisFrame = openedAmount == 1.0f && mOpenedAmount == 0.0f; - mOpenedAmount = openedAmount; - if (!mAmbientState.isPanelFullWidth() || mAmbientState.isDozing()) { - // We don't do a transformation at all, lets just assume we are fully opened - openedAmount = 1.0f; - } - int start = mRelativeOffset; - if (isLayoutRtl()) { - start = getWidth() - start - mCollapsedIcons.getWidth(); - } - int width = (int) NotificationUtils.interpolate( - start + mCollapsedIcons.getFinalTranslationX(), - mShelfIcons.getWidth(), - FAST_OUT_SLOW_IN_REVERSE.getInterpolation(openedAmount)); - mShelfIcons.setActualLayoutWidth(width); - boolean hasOverflow = mCollapsedIcons.hasOverflow(); - int collapsedPadding = mCollapsedIcons.getPaddingEnd(); - if (!hasOverflow) { - // we have to ensure that adding the low priority notification won't lead to an - // overflow - collapsedPadding -= mCollapsedIcons.getNoOverflowExtraPadding(); - } else { - // Partial overflow padding will fill enough space to add extra dots - collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding(); - } - float padding = NotificationUtils.interpolate(collapsedPadding, - mShelfIcons.getPaddingEnd(), - openedAmount); - mShelfIcons.setActualPaddingEnd(padding); - float paddingStart = NotificationUtils.interpolate(start, - mShelfIcons.getPaddingStart(), openedAmount); - mShelfIcons.setActualPaddingStart(paddingStart); - mShelfIcons.setOpenedAmount(openedAmount); - } - public void setMaxLayoutHeight(int maxLayoutHeight) { mMaxLayoutHeight = maxLayoutHeight; } @@ -1002,16 +785,11 @@ public class NotificationShelf extends ActivatableNotificationView implements return false; } - public void onUiModeChanged() { - updateBackgroundColors(); - } - public void setController(NotificationShelfController notificationShelfController) { mController = notificationShelfController; } private class ShelfState extends ExpandableViewState { - private float openedAmount; private boolean hasItemsInStableShelf; @Override @@ -1021,7 +799,6 @@ public class NotificationShelf extends ActivatableNotificationView implements } super.applyToView(view); - setOpenedAmount(openedAmount); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); @@ -1034,7 +811,6 @@ public class NotificationShelf extends ActivatableNotificationView implements } super.animateTo(child, properties); - setOpenedAmount(openedAmount); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 01d31039a749..e5a960e13e6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -83,6 +83,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi public static final int STATE_DOT = 1; public static final int STATE_HIDDEN = 2; + /** Maximum allowed width or height for an icon drawable */ + private static final int MAX_IMAGE_SIZE = 500; + private static final String TAG = "StatusBarIconView"; private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT = new FloatProperty<StatusBarIconView>("iconAppearAmount") { @@ -378,6 +381,13 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon); return false; } + + if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE + || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) { + Log.w(TAG, "Drawable is too large " + mIcon); + return false; + } + if (withClear) { setImageDrawable(null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index d1ab7ea55d57..b0d41f155713 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -180,9 +180,6 @@ class ConversationNotificationManager @Inject constructor( } if (changed) { notificationGroupManager.updateIsolation(entry) - // ensure that the conversation icon isn't hidden - // (ex: if it was showing in the shelf) - entry.row?.updateIconVisibilities() } } } @@ -221,13 +218,18 @@ class ConversationNotificationManager @Inject constructor( }) } + private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) = + if (notification.flags and Notification.FLAG_ONLY_ALERT_ONCE != 0) { + false + } else { + val oldBuilder = Notification.Builder.recoverBuilder(context, notification) + Notification.areStyledNotificationsVisiblyDifferent(oldBuilder, newBuilder) + } + fun getUnreadCount(entry: NotificationEntry, recoveredBuilder: Notification.Builder): Int = states.compute(entry.key) { _, state -> val newCount = state?.run { - val old = Notification.Builder.recoverBuilder(context, notification) - val increment = Notification - .areStyledNotificationsVisiblyDifferent(old, recoveredBuilder) - if (increment) unreadCount + 1 else unreadCount + if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1 else unreadCount } ?: 1 ConversationState(newCount, entry.sbn.notification) }!!.unreadCount 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 7c3b791aed09..c8c0755344a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -548,8 +548,6 @@ public class NotificationEntryManager implements try { mStatusBarService.onNotificationClear( notification.getPackageName(), - notification.getTag(), - notification.getId(), notification.getUser().getIdentifier(), notification.getKey(), dismissedByUserStats.dismissalSurface, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index 73c7fd1b64a3..f21771a89c9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -128,15 +128,6 @@ public class NotificationFilter { // this is a foreground-service disclosure for a user that does not need to show one return true; } - if (getFsc().isSystemAlertNotification(sbn)) { - final String[] apps = sbn.getNotification().extras.getStringArray( - Notification.EXTRA_FOREGROUND_APPS); - if (apps != null && apps.length >= 1) { - if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) { - return true; - } - } - } if (mIsMediaFlagEnabled && isMediaNotification(sbn)) { return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index d617dff372da..6b96ee4fc8e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -254,8 +254,6 @@ public class NotifCollection implements Dumpable { try { mStatusBarService.onNotificationClear( entry.getSbn().getPackageName(), - entry.getSbn().getTag(), - entry.getSbn().getId(), entry.getSbn().getUser().getIdentifier(), entry.getSbn().getKey(), stats.dismissalSurface, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 8a22b9f6891f..dbd8580b751e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -176,7 +176,6 @@ public final class NotificationEntry extends ListEntry { private int mBucket = BUCKET_ALERTING; @Nullable private Long mPendingAnimationDuration; private boolean mIsMarkedForUserTriggeredMovement; - private boolean mShelfIconVisible; private boolean mIsAlerting; public boolean mRemoteEditImeVisible; @@ -417,7 +416,6 @@ public final class NotificationEntry extends ListEntry { //TODO: This will go away when we have a way to bind an entry to a row public void setRow(ExpandableNotificationRow row) { this.row = row; - updateShelfIconVisibility(); } public ExpandableNotificationRowController getRowController() { @@ -938,19 +936,6 @@ public final class NotificationEntry extends ListEntry { return mIsMarkedForUserTriggeredMovement; } - /** Whether or not the icon for this notification is visible in the shelf. */ - public void setShelfIconVisible(boolean shelfIconVisible) { - if (row == null) return; - mShelfIconVisible = shelfIconVisible; - updateShelfIconVisibility(); - } - - private void updateShelfIconVisibility() { - if (row != null) { - row.setShelfIconVisible(mShelfIconVisible); - } - } - /** * Mark this entry for movement triggered by a user action (ex: changing the priorirty of a * conversation). This can then be used for custom animations. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java index 998ae9e55313..3a87f6853bcf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java @@ -95,18 +95,6 @@ public class AppOpsCoordinator implements Coordinator { sbn.getUser().getIdentifier())) { return true; } - - // Filters out system alert notifications when unneeded - if (mForegroundServiceController.isSystemAlertNotification(sbn)) { - final String[] apps = sbn.getNotification().extras.getStringArray( - Notification.EXTRA_FOREGROUND_APPS); - if (apps != null && apps.length >= 1) { - if (!mForegroundServiceController.isSystemAlertWarningNeeded( - sbn.getUser().getIdentifier(), apps[0])) { - return true; - } - } - } return false; } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index 18806effc545..5a3f48cec290 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -99,17 +99,11 @@ public class HighPriorityProvider { private boolean hasHighPriorityCharacteristics(NotificationEntry entry) { return !hasUserSetImportance(entry) - && (isImportantOngoing(entry) - || entry.getSbn().getNotification().hasMediaSession() + && (entry.getSbn().getNotification().hasMediaSession() || isPeopleNotification(entry) || isMessagingStyle(entry)); } - private boolean isImportantOngoing(NotificationEntry entry) { - return entry.getSbn().getNotification().isForegroundService() - && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW; - } - private boolean isMessagingStyle(NotificationEntry entry) { return Notification.MessagingStyle.class.equals( entry.getSbn().getNotification().getNotificationStyle()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index ba45f9a687ed..5375ac345e50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -95,11 +95,6 @@ class IconManager @Inject constructor( // Construct the shelf icon view. val shelfIcon = iconBuilder.createIconView(entry) shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE - - // TODO: This doesn't belong here - shelfIcon.setOnVisibilityChangedListener { newVisibility: Int -> - entry.setShelfIconVisible(newVisibility == View.VISIBLE) - } shelfIcon.visibility = View.INVISIBLE // Construct the aod icon view. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 0ad6507fb01e..dff97a679164 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.init +import android.content.Context +import android.provider.Settings import android.service.notification.StatusBarNotification import com.android.systemui.dagger.SysUISingleton import com.android.systemui.people.widget.PeopleSpaceWidgetManager @@ -58,6 +60,7 @@ import javax.inject.Inject */ @SysUISingleton class NotificationsControllerImpl @Inject constructor( + private val context: Context, private val featureFlags: FeatureFlags, private val notificationListener: NotificationListener, private val entryManager: NotificationEntryManager, @@ -129,7 +132,9 @@ class NotificationsControllerImpl @Inject constructor( entryManager.attach(notificationListener) } - if (featureFlags.isPeopleTileEnabled) { + val showPeopleSpace = Settings.Global.getInt(context.contentResolver, + Settings.Global.SHOW_PEOPLE_SPACE, 1) + if (showPeopleSpace == 1) { peopleSpaceWidgetManager.attach(notificationListener) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 724921b3f7c8..31d052d75998 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -191,7 +191,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView initDimens(); } - protected void updateBackgroundColors() { + /** + * Reload background colors from resources and invalidate views. + */ + public void updateBackgroundColors() { updateColors(); initBackground(); updateBackgroundTint(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 1251b58171da..cde9f38d0074 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -166,7 +166,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mMaxSmallHeightLarge; private int mMaxSmallHeightMedia; private int mMaxExpandedHeight; - private int mMaxCallHeight; private int mIncreasedPaddingBetweenElements; private int mNotificationLaunchHeight; private boolean mMustStayOnScreen; @@ -331,7 +330,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mHeadsupDisappearRunning; private View mChildAfterViewWhenDismissed; private View mGroupParentWhenDismissed; - private boolean mShelfIconVisible; private boolean mAboveShelf; private OnUserInteractionCallback mOnUserInteractionCallback; private NotificationGutsManager mNotificationGutsManager; @@ -568,7 +566,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // The public layouts expand button is always visible mPublicLayout.updateExpandButtons(true); updateLimits(); - updateIconVisibilities(); updateShelfIconColor(); updateRippleAllowed(); if (mUpdateBackgroundOnUpdate) { @@ -689,7 +686,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // them a headerless design, then remove this hack. smallHeight = mMaxSmallHeightLarge; } else if (isCallLayout) { - smallHeight = mMaxCallHeight; + smallHeight = mMaxExpandedHeight; } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) { smallHeight = mMaxSmallHeightLarge; } else { @@ -883,7 +880,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView setDistanceToTopRoundness(NO_ROUNDNESS); mNotificationParent.updateBackgroundForGroupState(); } - updateIconVisibilities(); updateBackgroundClipping(); } @@ -1481,21 +1477,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getShelfTransformationTarget() != null; } - /** - * Set the icons to be visible of this notification. - */ - public void setShelfIconVisible(boolean iconVisible) { - if (iconVisible != mShelfIconVisible) { - mShelfIconVisible = iconVisible; - updateIconVisibilities(); - } - } - - @Override - protected void onBelowSpeedBumpChanged() { - updateIconVisibilities(); - } - @Override protected void updateContentTransformation() { if (mExpandAnimationRunning) { @@ -1522,18 +1503,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Refreshes the visibility of notification icons */ - public void updateIconVisibilities() { - // The shelf icon is never hidden for children in groups - boolean visible = !isChildInGroup() && mShelfIconVisible; - for (NotificationContentView l : mLayouts) { - l.setShelfIconVisible(visible); - } - if (mChildrenContainer != null) { - mChildrenContainer.setShelfIconVisible(visible); - } - } - public void setIsLowPriority(boolean isLowPriority) { mIsLowPriority = isLowPriority; mPrivateLayout.setIsLowPriority(isLowPriority); @@ -1651,8 +1620,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_min_height_media); mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_height); - mMaxCallHeight = NotificationUtils.getFontScaledHeight(mContext, - R.dimen.call_notification_full_height); mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_heads_up_height_legacy); mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, @@ -2052,8 +2019,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationLaunchHeight, zProgress); setTranslationZ(translationZ); - float extraWidthForClipping = params.getWidth() - getWidth() - + MathUtils.lerp(0, mOutlineRadius * 2, params.getProgress()); + float extraWidthForClipping = params.getWidth() - getWidth(); setExtraWidthForClipping(extraWidthForClipping); int top = params.getTop(); float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index ba03d01b20b0..73e080423d40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -69,7 +69,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { private float mContentTranslation; protected boolean mLastInSection; protected boolean mFirstInSection; - boolean mIsBeingSwiped; public ExpandableView(Context context, AttributeSet attrs) { super(context, attrs); @@ -174,14 +173,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { return false; } - public void setIsBeingSwiped(boolean swiped) { - mIsBeingSwiped = swiped; - } - - public boolean isBeingSwiped() { - return mIsBeingSwiped; - } - public boolean isHeadsUpAnimatingAway() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index f427ba958b8f..d3065aa36a5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -17,15 +17,13 @@ package com.android.systemui.statusbar.notification.row; -import static android.provider.Settings.Global.NOTIFICATION_BUBBLES; -import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; @@ -34,6 +32,7 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; @@ -173,7 +172,6 @@ public class NotificationContentView extends FrameLayout { private int mContentHeightAtAnimationStart = UNDEFINED; private boolean mFocusOnVisibilityChange; private boolean mHeadsUpAnimatingAway; - private boolean mShelfIconVisible; private int mClipBottomAmount; private boolean mIsLowPriority; private boolean mIsContentExpandable; @@ -878,10 +876,12 @@ public class NotificationContentView extends FrameLayout { public void setBackgroundTintColor(int color) { if (mExpandedSmartReplyView != null) { - mExpandedSmartReplyView.setBackgroundTintColor(color); + boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized(); + mExpandedSmartReplyView.setBackgroundTintColor(color, colorized); } if (mHeadsUpSmartReplyView != null) { - mHeadsUpSmartReplyView.setBackgroundTintColor(color); + boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized(); + mHeadsUpSmartReplyView.setBackgroundTintColor(color, colorized); } } @@ -1238,6 +1238,7 @@ public class NotificationContentView extends FrameLayout { mCachedHeadsUpRemoteInput = null; } + private RemoteInputView applyRemoteInput(View view, NotificationEntry entry, boolean hasRemoteInput, PendingIntent existingPendingIntent, RemoteInputView cachedView, NotificationViewWrapper wrapper) { @@ -1275,6 +1276,15 @@ public class NotificationContentView extends FrameLayout { if (color == Notification.COLOR_DEFAULT) { color = mContext.getColor(R.color.default_remote_input_background); } + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_tintNotificationsWithTheme)) { + Resources.Theme theme = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); + TypedArray ta = theme.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.colorAccent}); + color = ta.getColor(0, color); + ta.recycle(); + } existing.setBackgroundColor(ContrastColorUtil.ensureTextBackgroundColor(color, mContext.getColor(R.color.remote_input_text_enabled), mContext.getColor(R.color.remote_input_hint))); @@ -1317,7 +1327,7 @@ public class NotificationContentView extends FrameLayout { private boolean isBubblesEnabled() { return Settings.Global.getInt(mContext.getContentResolver(), - NOTIFICATION_BUBBLES, 0) == 1; + Settings.Global.NOTIFICATION_BUBBLES, 0) == 1; } /** @@ -1346,12 +1356,10 @@ public class NotificationContentView extends FrameLayout { && isPersonWithShortcut && entry.getBubbleMetadata() != null; if (showButton) { - Drawable d = mContext.getResources().getDrawable(entry.isBubble() + // explicitly resolve drawable resource using SystemUI's theme + Drawable d = mContext.getDrawable(entry.isBubble() ? R.drawable.bubble_ic_stop_bubble : R.drawable.bubble_ic_create_bubble); - mContainingNotification.updateNotificationColor(); - final int tint = mContainingNotification.getNotificationColor(); - d.setTint(tint); String contentDescription = mContext.getResources().getString(entry.isBubble() ? R.string.notification_conversation_unbubble @@ -1373,27 +1381,25 @@ public class NotificationContentView extends FrameLayout { } ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button); View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); - LinearLayout actionContainerLayout = - layout.findViewById(com.android.internal.R.id.actions_container_layout); - if (snoozeButton == null || actionContainer == null || actionContainerLayout == null) { + if (snoozeButton == null || actionContainer == null) { return; } final boolean showSnooze = Settings.Secure.getInt(mContext.getContentResolver(), - SHOW_NOTIFICATION_SNOOZE, 0) == 1; - if (!showSnooze) { + Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1; + // Notification.Builder can 'disable' the snooze button to prevent it from being shown here + boolean snoozeDisabled = !snoozeButton.isEnabled(); + if (!showSnooze || snoozeDisabled) { snoozeButton.setVisibility(GONE); return; } - Resources res = mContext.getResources(); - Drawable snoozeDrawable = res.getDrawable(R.drawable.ic_snooze); - mContainingNotification.updateNotificationColor(); - snoozeDrawable.setTint(mContainingNotification.getNotificationColor()); + // explicitly resolve drawable resource using SystemUI's theme + Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze); snoozeButton.setImageDrawable(snoozeDrawable); final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext) .inflate(R.layout.notification_snooze, null, false); - final String snoozeDescription = res.getString( + final String snoozeDescription = mContext.getString( R.string.notification_menu_snooze_description); final NotificationMenuRowPlugin.MenuItem snoozeMenuItem = new NotificationMenuRow.NotificationMenuItem( @@ -1505,7 +1511,9 @@ public class NotificationContentView extends FrameLayout { smartReplyView.addPreInflatedButtons( inflatedSmartReplyViewHolder.getSmartSuggestionButtons()); // Ensure the colors of the smart suggestion buttons are up-to-date. - smartReplyView.setBackgroundTintColor(entry.getRow().getCurrentBackgroundTint()); + int backgroundColor = entry.getRow().getCurrentBackgroundTint(); + boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized(); + smartReplyView.setBackgroundTintColor(backgroundColor, colorized); smartReplyContainer.setVisibility(View.VISIBLE); } return smartReplyView; @@ -1734,23 +1742,6 @@ public class NotificationContentView extends FrameLayout { mFocusOnVisibilityChange = true; } - public void setShelfIconVisible(boolean iconsVisible) { - mShelfIconVisible = iconsVisible; - updateIconVisibilities(); - } - - private void updateIconVisibilities() { - if (mContractedWrapper != null) { - mContractedWrapper.setShelfIconVisible(mShelfIconVisible); - } - if (mHeadsUpWrapper != null) { - mHeadsUpWrapper.setShelfIconVisible(mShelfIconVisible); - } - if (mExpandedWrapper != null) { - mExpandedWrapper.setShelfIconVisible(mShelfIconVisible); - } - } - @Override public void onVisibilityAggregated(boolean isVisible) { super.onVisibilityAggregated(isVisible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java index 7248bcef621c..151840afcef1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java @@ -44,9 +44,10 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl private void updateImageTag(StatusBarNotification notification) { final Bundle extras = notification.getNotification().extras; - Icon overRiddenIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG); - if (overRiddenIcon != null) { - mPicture.setTag(ImageTransformState.ICON_TAG, overRiddenIcon); + Icon overriddenIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG); + if (overriddenIcon != null) { + mRightIcon.setTag(ImageTransformState.ICON_TAG, overriddenIcon); + mLeftIcon.setTag(ImageTransformState.ICON_TAG, overriddenIcon); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt index 4541ebf4c4f2..12e94cbc1ab9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt @@ -34,7 +34,7 @@ class NotificationCallTemplateViewWrapper constructor( ) : NotificationTemplateViewWrapper(ctx, view, row) { private val minHeightWithActions: Int = - NotificationUtils.getFontScaledHeight(ctx, R.dimen.call_notification_full_height) + NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height) private val callLayout: CallLayout = view as CallLayout private lateinit var conversationIconView: CachingIconView diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index 905bccfa6cdf..fb0fdcccd4b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -132,20 +132,6 @@ class NotificationConversationTemplateViewWrapper constructor( ) } - override fun setShelfIconVisible(visible: Boolean) { - if (conversationLayout.isImportantConversation) { - if (conversationIconView.visibility != View.GONE) { - conversationIconView.isForceHidden = visible - // We don't want the small icon to be hidden by the extended wrapper, as force - // hiding the conversationIcon will already do that via its listener. - return - } - } else { - conversationIconView.isForceHidden = false - } - super.setShelfIconVisible(visible) - } - override fun getShelfTransformationTarget(): View? = if (conversationLayout.isImportantConversation) if (conversationIconView.visibility != View.GONE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java index 414d62092ab2..222735aeb35a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java @@ -49,7 +49,7 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { // Custom views will most likely use just white or black as their text color. // We need to scan through and replace these colors by Material NEXT colors. - ensureThemeOnChildren(); + ensureThemeOnChildren(mView); // Let's invert the notification colors when we're in night mode and // the notification background isn't colorized. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java index 301c3726793a..d21ae13a1e01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java @@ -64,7 +64,7 @@ public class NotificationDecoratedCustomViewWrapper extends NotificationTemplate // Custom views will most likely use just white or black as their text color. // We need to scan through and replace these colors by Material NEXT colors. - ensureThemeOnChildren(); + ensureThemeOnChildren(mWrappedView); if (needsInversion(resolveBackgroundColor(), mWrappedView)) { invertViewLuminosity(mWrappedView); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index eb79e3c8a69a..bdafd232167d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -313,12 +313,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override - public void setShelfIconVisible(boolean visible) { - super.setShelfIconVisible(visible); - mIcon.setForceHidden(visible); - } - - @Override public TransformState getCurrentState(int fadingView) { return mTransformationHelper.getCurrentState(fadingView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java index e9934c0053b0..e0b58125aabd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java @@ -51,7 +51,8 @@ import com.android.systemui.statusbar.notification.row.HybridNotificationView; public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper { private final int mFullHeaderTranslation; - protected ImageView mPicture; + protected ImageView mRightIcon; + protected ImageView mLeftIcon; private ProgressBar mProgressBar; private TextView mTitle; private TextView mText; @@ -140,9 +141,14 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp } private void resolveTemplateViews(StatusBarNotification notification) { - mPicture = mView.findViewById(com.android.internal.R.id.right_icon); - if (mPicture != null) { - mPicture.setTag(ImageTransformState.ICON_TAG, + mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon); + if (mRightIcon != null) { + mRightIcon.setTag(ImageTransformState.ICON_TAG, + notification.getNotification().getLargeIcon()); + } + mLeftIcon = mView.findViewById(com.android.internal.R.id.left_icon); + if (mLeftIcon != null) { + mLeftIcon.setTag(ImageTransformState.ICON_TAG, notification.getNotification().getLargeIcon()); } mTitle = mView.findViewById(com.android.internal.R.id.title); @@ -240,9 +246,9 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp resolveTemplateViews(row.getEntry().getSbn()); super.onContentUpdated(row); // With the modern templates, a large icon visually overlaps the header, so we can't - // simply hide the header -- just show the + // hide the header, we must show it. mCanHideHeader = mNotificationHeader != null - && (mPicture == null || mPicture.getVisibility() != VISIBLE); + && (mRightIcon == null || mRightIcon.getVisibility() != VISIBLE); if (row.getHeaderVisibleAmount() != DEFAULT_HEADER_VISIBLE_AMOUNT) { setHeaderVisibleAmount(row.getHeaderVisibleAmount()); } @@ -260,14 +266,15 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText); } - if (mPicture != null) { + if (mRightIcon != null) { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, - mPicture); + mRightIcon); } if (mProgressBar != null) { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar); } + addViewsTransformingToSimilar(mLeftIcon); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 5fff8c83048f..9ced12d32d27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -36,12 +36,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.settingslib.Utils; +import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.notification.TransformState; @@ -58,9 +58,11 @@ public abstract class NotificationViewWrapper implements TransformableView { private final Rect mTmpRect = new Rect(); protected int mBackgroundColor = 0; - private int mLightTextColor; - private int mDarkTextColor; - private int mDefaultTextColor; + private int mMaterialTextColorPrimary; + private int mMaterialTextColorSecondary; + private int mThemedTextColorPrimary; + private int mThemedTextColorSecondary; + private boolean mAdjustTheme; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { @@ -97,6 +99,8 @@ public abstract class NotificationViewWrapper implements TransformableView { mView = view; mRow = row; onReinflated(); + mAdjustTheme = ctx.getResources().getBoolean( + R.bool.config_adjustThemeOnNotificationCustomViews); } /** @@ -121,15 +125,22 @@ public abstract class NotificationViewWrapper implements TransformableView { mBackgroundColor = backgroundColor; mView.setBackground(new ColorDrawable(Color.TRANSPARENT)); } - mLightTextColor = mView.getContext().getColor( - com.android.internal.R.color.notification_primary_text_color_light); - mDarkTextColor = mView.getContext().getColor( - R.color.notification_primary_text_color_dark); + + Context materialTitleContext = new ContextThemeWrapper(mView.getContext(), + com.android.internal.R.style.TextAppearance_Material_Notification_Title); + mMaterialTextColorPrimary = Utils.getColorAttr(materialTitleContext, + com.android.internal.R.attr.textColor).getDefaultColor(); + Context materialContext = new ContextThemeWrapper(mView.getContext(), + com.android.internal.R.style.TextAppearance_Material_Notification); + mMaterialTextColorSecondary = Utils.getColorAttr(materialContext, + com.android.internal.R.attr.textColor).getDefaultColor(); Context themedContext = new ContextThemeWrapper(mView.getContext(), - R.style.Theme_DeviceDefault_DayNight); - mDefaultTextColor = Utils.getColorAttr(themedContext, R.attr.textColorPrimary) - .getDefaultColor(); + com.android.internal.R.style.Theme_DeviceDefault_DayNight); + mThemedTextColorPrimary = Utils.getColorAttr(themedContext, + com.android.internal.R.attr.textColorPrimary).getDefaultColor(); + mThemedTextColorSecondary = Utils.getColorAttr(themedContext, + com.android.internal.R.attr.textColorSecondary).getDefaultColor(); } protected boolean needsInversion(int defaultBackgroundColor, View view) { @@ -207,38 +218,35 @@ public abstract class NotificationViewWrapper implements TransformableView { return false; } - protected void ensureThemeOnChildren() { - if (mView == null) { + protected void ensureThemeOnChildren(View rootView) { + if (!mAdjustTheme || mView == null || rootView == null) { return; } // Notifications with custom backgrounds should not be adjusted if (mBackgroundColor != Color.TRANSPARENT - || getBackgroundColor(mView) != Color.TRANSPARENT) { + || getBackgroundColor(mView) != Color.TRANSPARENT + || getBackgroundColor(rootView) != Color.TRANSPARENT) { return; } // Now let's check if there's unprotected text somewhere, and apply the theme if we find it. - if (!(mView instanceof ViewGroup)) { - return; - } - processChildrenTextColor((ViewGroup) mView); + processTextColorRecursive(rootView); } - private void processChildrenTextColor(ViewGroup viewGroup) { - if (viewGroup == null) { - return; - } - - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - if (child instanceof TextView) { - int foreground = ((TextView) child).getCurrentTextColor(); - if (foreground == mLightTextColor || foreground == mDarkTextColor) { - ((TextView) child).setTextColor(mDefaultTextColor); - } - } else if (child instanceof ViewGroup) { - processChildrenTextColor((ViewGroup) child); + private void processTextColorRecursive(View view) { + if (view instanceof TextView) { + TextView textView = (TextView) view; + int foreground = textView.getCurrentTextColor(); + if (foreground == mMaterialTextColorPrimary) { + textView.setTextColor(mThemedTextColorPrimary); + } else if (foreground == mMaterialTextColorSecondary) { + textView.setTextColor(mThemedTextColorSecondary); + } + } else if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + processTextColorRecursive(viewGroup.getChildAt(i)); } } } @@ -323,11 +331,6 @@ public abstract class NotificationViewWrapper implements TransformableView { return null; } - /** - * Set the shelf icon to be visible and hide our own icons. - */ - public void setShelfIconVisible(boolean shelfIconVisible) {} - public int getHeaderTranslation(boolean forceNoHeader) { return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 756fe6c5ba24..8446b4e6a3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -133,7 +133,7 @@ public class AmbientState { */ public static int getNotificationLaunchHeight(Context context) { int zDistance = getZDistanceBetweenElements(context); - return getBaseHeight(zDistance) * 2; + return NOTIFICATIONS_HAVE_SHADOWS ? 2 * getBaseHeight(zDistance) : 4 * zDistance; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 3833637e8542..d8ee102064e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -20,10 +20,12 @@ import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.drawable.ColorDrawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Pair; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.NotificationHeaderView; import android.view.View; @@ -33,6 +35,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.CachingIconView; +import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationGroupingUtil; @@ -103,6 +106,8 @@ public class NotificationChildrenContainer extends ViewGroup { private ViewGroup mCurrentHeader; private boolean mIsConversation; + private boolean mTintWithThemeAccent; + private boolean mShowGroupCountInExpander; private boolean mShowDividersWhenExpanded; private boolean mHideDividersDuringExpand; private int mTranslationForHeader; @@ -145,6 +150,10 @@ public class NotificationChildrenContainer extends ViewGroup { com.android.internal.R.dimen.notification_content_margin); mEnableShadowOnChildNotifications = res.getBoolean(R.bool.config_enableShadowOnChildNotifications); + mTintWithThemeAccent = + res.getBoolean(com.android.internal.R.bool.config_tintNotificationsWithTheme); + mShowGroupCountInExpander = + res.getBoolean(R.bool.config_showNotificationGroupCountInExpander); mShowDividersWhenExpanded = res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded); mHideDividersDuringExpand = @@ -229,7 +238,6 @@ public class NotificationChildrenContainer extends ViewGroup { mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec); } if (mNotificationHeaderLowPriority != null) { - headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec); } @@ -397,7 +405,20 @@ public class NotificationChildrenContainer extends ViewGroup { mGroupingUtil.updateChildrenAppearance(); } + private void setExpandButtonNumber(NotificationViewWrapper wrapper) { + View expandButton = wrapper == null + ? null : wrapper.getExpandButton(); + if (expandButton instanceof NotificationExpandButton) { + ((NotificationExpandButton) expandButton).setNumber(mUntruncatedChildCount); + } + } + public void updateGroupOverflow() { + if (mShowGroupCountInExpander) { + setExpandButtonNumber(mNotificationHeaderWrapper); + setExpandButtonNumber(mNotificationHeaderWrapperLowPriority); + return; + } int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); if (mUntruncatedChildCount > maxAllowedVisibleChildren) { int number = mUntruncatedChildCount - maxAllowedVisibleChildren; @@ -1201,8 +1222,21 @@ public class NotificationChildrenContainer extends ViewGroup { } public void onNotificationUpdated() { - mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, - mContainingNotification.getNotificationColor()); + if (mShowGroupCountInExpander) { + // The overflow number is not used, so its color is irrelevant; skip this + return; + } + int color = mContainingNotification.getNotificationColor(); + if (mTintWithThemeAccent) { + // We're using the theme accent, color with the accent color instead of the notif color + Resources.Theme theme = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); + TypedArray ta = theme.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.colorAccent}); + color = ta.getColor(0, color); + ta.recycle(); + } + mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color); } public int getPositionInLinearLayout(View childInGroup) { @@ -1225,21 +1259,6 @@ public class NotificationChildrenContainer extends ViewGroup { return 0; } - public void setShelfIconVisible(boolean iconVisible) { - if (mNotificationHeaderWrapper != null) { - CachingIconView icon = mNotificationHeaderWrapper.getIcon(); - if (icon != null) { - icon.setForceHidden(iconVisible); - } - } - if (mNotificationHeaderWrapperLowPriority != null) { - CachingIconView icon = mNotificationHeaderWrapperLowPriority.getIcon(); - if (icon != null) { - icon.setForceHidden(iconVisible); - } - } - } - public void setClipBottomAmount(int clipBottomAmount) { mClipBottomAmount = clipBottomAmount; updateChildrenClipping(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 45f5b3136b9b..b1ac12e84fb3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack; import android.content.res.Resources; import android.util.MathUtils; +import android.view.View; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; @@ -47,6 +48,10 @@ public class NotificationRoundnessManager { private ExpandableNotificationRow mTrackedHeadsUp; private float mAppearFraction; + private ExpandableView mSwipedView = null; + private ExpandableView mViewBeforeSwipedView = null; + private ExpandableView mViewAfterSwipedView = null; + @Inject NotificationRoundnessManager( KeyguardBypassController keyguardBypassController, @@ -68,6 +73,11 @@ public class NotificationRoundnessManager { boolean updateViewWithoutCallback(ExpandableView view, boolean animate) { + if (view == null + || view == mViewBeforeSwipedView + || view == mViewAfterSwipedView) { + return false; + } float topRoundness = getRoundness(view, true /* top */); float bottomRoundness = getRoundness(view, false /* top */); boolean topChanged = view.setTopRoundness(topRoundness, animate); @@ -105,9 +115,60 @@ public class NotificationRoundnessManager { return false; } + void setViewsAffectedBySwipe( + ExpandableView viewBefore, + ExpandableView viewSwiped, + ExpandableView viewAfter, + boolean cornerAnimationsEnabled) { + if (!cornerAnimationsEnabled) { + return; + } + final boolean animate = true; + + ExpandableView oldViewBefore = mViewBeforeSwipedView; + mViewBeforeSwipedView = viewBefore; + if (oldViewBefore != null) { + final float bottomRoundness = getRoundness(oldViewBefore, false /* top */); + oldViewBefore.setBottomRoundness(bottomRoundness, animate); + } + if (viewBefore != null) { + viewBefore.setBottomRoundness(1f, animate); + } + + ExpandableView oldSwipedview = mSwipedView; + mSwipedView = viewSwiped; + if (oldSwipedview != null) { + final float bottomRoundness = getRoundness(oldSwipedview, false /* top */); + final float topRoundness = getRoundness(oldSwipedview, true /* top */); + oldSwipedview.setTopRoundness(topRoundness, animate); + oldSwipedview.setBottomRoundness(bottomRoundness, animate); + } + if (viewSwiped != null) { + viewSwiped.setTopRoundness(1f, animate); + viewSwiped.setBottomRoundness(1f, animate); + } + + ExpandableView oldViewAfter = mViewAfterSwipedView; + mViewAfterSwipedView = viewAfter; + if (oldViewAfter != null) { + final float topRoundness = getRoundness(oldViewAfter, true /* top */); + oldViewAfter.setTopRoundness(topRoundness, animate); + } + if (viewAfter != null) { + viewAfter.setTopRoundness(1f, animate); + } + } + private float getRoundness(ExpandableView view, boolean top) { + if (view == null) { + return 0f; + } + if (view == mViewBeforeSwipedView + || view == mSwipedView + || view == mViewAfterSwipedView) { + return 1f; + } if ((view.isPinned() - || view.isBeingSwiped() || (view.isHeadsUpAnimatingAway()) && !mExpanded)) { return 1.0f; } 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 2c7c5cc91120..970efd5cbfe2 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 @@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; +import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -83,6 +84,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper.DragDownCallback; import com.android.systemui.statusbar.EmptyShadeView; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; @@ -453,6 +455,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private NotificationEntry mTopHeadsUpEntry; private long mNumHeadsUp; private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; + private final FeatureFlags mFeatureFlags; private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = new ExpandableView.OnHeightChangedListener() { @@ -492,8 +495,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable GroupMembershipManager groupMembershipManager, GroupExpansionManager groupExpansionManager, SysuiStatusBarStateController statusbarStateController, - AmbientState ambientState - ) { + AmbientState ambientState, + FeatureFlags featureFlags) { super(context, attrs, 0, 0); Resources res = getResources(); mSectionsManager = notificationSectionsManager; @@ -530,6 +533,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mGroupMembershipManager = groupMembershipManager; mGroupExpansionManager = groupExpansionManager; mStatusbarStateController = statusbarStateController; + mFeatureFlags = featureFlags; } void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) { @@ -622,7 +626,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating) .getDefaultColor(); updateBackgroundDimming(); - mShelf.onUiModeChanged(); + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ActivatableNotificationView) { + ((ActivatableNotificationView) child).updateBackgroundColors(); + } + } } @ShadeViewRefactor(RefactorComponent.DECORATOR) @@ -1016,8 +1025,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean clip = clipStart > start && clipStart < end || clipEnd >= start && clipEnd <= end; clip &= !(first && mScrollAdapter.isScrolledToTop()); - child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) - : ExpandableView.NO_ROUNDNESS); + child.setDistanceToTopRoundness(ExpandableView.NO_ROUNDNESS); first = false; } } @@ -1156,8 +1164,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (stackStartPosition <= stackEndPosition) { stackHeight = stackEndPosition; } else { - stackHeight = (int) NotificationUtils.interpolate(stackStartPosition, - stackEndPosition, mQsExpansionFraction); + if (shouldUseSplitNotificationShade(mFeatureFlags, getResources())) { + // This prevents notifications from being collapsed when QS is expanded. + stackHeight = (int) height; + } else { + stackHeight = (int) NotificationUtils.interpolate(stackStartPosition, + stackEndPosition, mQsExpansionFraction); + } } } else { stackHeight = (int) height; @@ -2278,9 +2291,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable ExpandableView child = (ExpandableView) getChildAt(i); if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) - && child != mShelf - && (mSwipeHelper.getSwipedView() != child - || !child.getResources().getBoolean(R.bool.flag_notif_updates))) { + && child != mShelf) { children.add(child); } } @@ -4979,28 +4990,48 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSwipedOutViews.add(v); } - void onSwipeBegin(View v) { - if (v instanceof ExpandableView) { - ExpandableView ev = (ExpandableView) v; - ev.setIsBeingSwiped(true); - mController.getNoticationRoundessManager() - .updateViewWithoutCallback(ev, true /* animate */); + void onSwipeBegin(View viewSwiped) { + if (!(viewSwiped instanceof ExpandableNotificationRow)) { + return; } - requestDisallowInterceptTouchEvent(true); + final int indexOfSwipedView = indexOfChild(viewSwiped); + if (indexOfSwipedView < 0) { + return; + } + mSectionsManager.updateFirstAndLastViewsForAllSections( + mSections, getChildrenWithBackground()); + View viewBefore = null; + if (indexOfSwipedView > 0) { + viewBefore = getChildAt(indexOfSwipedView - 1); + if (mSectionsManager.beginsSection(viewSwiped, viewBefore)) { + viewBefore = null; + } + } + View viewAfter = null; + if (indexOfSwipedView < getChildCount()) { + viewAfter = getChildAt(indexOfSwipedView + 1); + if (mSectionsManager.beginsSection(viewAfter, viewSwiped)) { + viewAfter = null; + } + } + mController.getNoticationRoundessManager() + .setViewsAffectedBySwipe((ExpandableView) viewBefore, + (ExpandableView) viewSwiped, + (ExpandableView) viewAfter, + getResources().getBoolean(R.bool.flag_notif_updates)); + updateFirstAndLastBackgroundViews(); + requestDisallowInterceptTouchEvent(true); updateContinuousShadowDrawing(); updateContinuousBackgroundDrawing(); requestChildrenUpdate(); } - void onSwipeEnd(View v) { - if (v instanceof ExpandableView) { - ExpandableView ev = (ExpandableView) v; - ev.setIsBeingSwiped(false); - mController.getNoticationRoundessManager() - .updateViewWithoutCallback(ev, true /* animate */); - } + void onSwipeEnd() { updateFirstAndLastBackgroundViews(); + mController.getNoticationRoundessManager() + .setViewsAffectedBySwipe(null, null, null, + getResources().getBoolean(R.bool.flag_notif_updates)); } void setTopHeadsUpEntry(NotificationEntry topEntry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index a51674280c1c..7baad1cc265e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -223,6 +223,7 @@ public class NotificationStackScrollLayoutController { updateShowEmptyShadeView(); mView.updateCornerRadius(); mView.updateBgColor(); + mView.updateDecorViews(); mView.reinflateViews(); } @@ -398,7 +399,7 @@ public class NotificationStackScrollLayoutController { if (mView.getDismissAllInProgress()) { return; } - mView.onSwipeEnd(view); + mView.onSwipeEnd(); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (row.isHeadsUp()) { @@ -458,7 +459,7 @@ public class NotificationStackScrollLayoutController { @Override public void onChildSnappedBack(View animView, float targetLeft) { - mView.onSwipeEnd(animView); + mView.onSwipeEnd(); if (animView instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) animView; if (row.isPinned() && !canChildBeDismissed(row) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index e40c262765ea..204dd9f5e58c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -14,6 +14,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; + import android.content.Context; import android.content.res.Resources; import android.hardware.display.ColorDisplayManager; @@ -27,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.SecureSetting; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.statusbar.policy.CastController; @@ -41,6 +44,8 @@ import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; import java.util.Objects; +import javax.inject.Named; + /** * Manages which tiles should be automatically added to QS. */ @@ -69,6 +74,8 @@ public class AutoTileManager implements UserAwareController { private final ManagedProfileController mManagedProfileController; private final NightDisplayListener mNightDisplayListener; private final CastController mCastController; + private final ReduceBrightColorsController mReduceBrightColorsController; + private final boolean mIsReduceBrightColorsAvailable; private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>(); public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, @@ -79,7 +86,9 @@ public class AutoTileManager implements UserAwareController { DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, - CastController castController) { + CastController castController, + ReduceBrightColorsController reduceBrightColorsController, + @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { mContext = context; mHost = host; mSecureSettings = secureSettings; @@ -91,6 +100,8 @@ public class AutoTileManager implements UserAwareController { mManagedProfileController = managedProfileController; mNightDisplayListener = nightDisplayListener; mCastController = castController; + mReduceBrightColorsController = reduceBrightColorsController; + mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable; } /** @@ -124,9 +135,9 @@ public class AutoTileManager implements UserAwareController { if (!mAutoTracker.isAdded(CAST)) { mCastController.addCallback(mCastCallback); } - - // TODO(b/170970675): Set a listener/controller and callback for Reduce Bright Colors - // state changes. Call into ColorDisplayService to get availability/config status + if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) { + mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback); + } int settingsN = mAutoAddSettingList.size(); for (int i = 0; i < settingsN; i++) { @@ -143,6 +154,9 @@ public class AutoTileManager implements UserAwareController { if (ColorDisplayManager.isNightDisplayAvailable(mContext)) { mNightDisplayListener.setCallback(null); } + if (mIsReduceBrightColorsAvailable) { + mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback); + } mCastController.removeCallback(mCastCallback); int settingsN = mAutoAddSettingList.size(); for (int i = 0; i < settingsN; i++) { @@ -287,6 +301,24 @@ public class AutoTileManager implements UserAwareController { }; @VisibleForTesting + final ReduceBrightColorsController.Listener mReduceBrightColorsCallback = + new ReduceBrightColorsController.Listener() { + @Override + public void onActivated(boolean activated) { + if (activated) { + addReduceBrightColorsTile(); + } + } + + private void addReduceBrightColorsTile() { + if (mAutoTracker.isAdded(BRIGHTNESS)) return; + mHost.addTile(BRIGHTNESS); + mAutoTracker.setTileAdded(BRIGHTNESS); + mHandler.post(() -> mReduceBrightColorsController.removeCallback(this)); + } + }; + + @VisibleForTesting final CastController.Callback mCastCallback = new CastController.Callback() { @Override public void onCastDevicesChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 8cdaa63994e4..f4830fbb0028 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -134,7 +134,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, mStackScrollerController = stackScrollerController; mNotificationPanelViewController = notificationPanelViewController; notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp); - notificationPanelViewController.addVerticalTranslationListener(mUpdatePanelTranslation); + notificationPanelViewController.setVerticalTranslationListener(mUpdatePanelTranslation); notificationPanelViewController.setHeadsUpAppearanceController(this); mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight); mStackScrollerController.addOnLayoutChangeListener(mStackScrollLayoutChangeListener); @@ -171,7 +171,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null); mWakeUpCoordinator.removeListener(this); mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp); - mNotificationPanelViewController.removeVerticalTranslationListener(mUpdatePanelTranslation); + mNotificationPanelViewController.setVerticalTranslationListener(null); mNotificationPanelViewController.setHeadsUpAppearanceController(null); mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight); mStackScrollerController.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener); 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 b6c713cadeb5..6b5a908fc09b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; -import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK; @@ -75,10 +74,6 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.controls.dagger.ControlsComponent; -import com.android.systemui.controls.ui.ControlsDialog; -import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.IntentButtonProvider; import com.android.systemui.plugins.IntentButtonProvider.IntentButton; @@ -133,7 +128,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private KeyguardAffordanceView mRightAffordanceView; private KeyguardAffordanceView mLeftAffordanceView; - private ImageView mAltLeftButton; + private ImageView mWalletButton; private ViewGroup mIndicationArea; private TextView mIndicationText; private TextView mIndicationTextBottom; @@ -182,11 +177,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private int mBurnInXOffset; private int mBurnInYOffset; private ActivityIntentHelper mActivityIntentHelper; - - private ControlsDialog mControlsDialog; - private ControlsComponent mControlsComponent; private int mLockScreenMode; - private BroadcastDispatcher mBroadcastDispatcher; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; public KeyguardBottomAreaView(Context context) { @@ -255,7 +246,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mOverlayContainer = findViewById(R.id.overlay_container); mRightAffordanceView = findViewById(R.id.camera_button); mLeftAffordanceView = findViewById(R.id.left_button); - mAltLeftButton = findViewById(R.id.alt_left_button); + mWalletButton = findViewById(R.id.wallet_button); mIndicationArea = findViewById(R.id.keyguard_indication_area); mIndicationText = findViewById(R.id.keyguard_indication_text); mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom); @@ -355,10 +346,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setLayoutParams(lp); updateLeftAffordanceIcon(); - lp = mAltLeftButton.getLayoutParams(); + lp = mWalletButton.getLayoutParams(); lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width); lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height); - mAltLeftButton.setLayoutParams(lp); + mWalletButton.setLayoutParams(lp); } private void updateRightAffordanceIcon() { @@ -431,11 +422,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setContentDescription(state.contentDescription); } - private void updateControlsVisibility() { - if (mDozing || mControlsComponent.getVisibility() != AVAILABLE) { - mAltLeftButton.setVisibility(GONE); + private void updateWalletVisibility() { + if (mDozing) { + mWalletButton.setVisibility(GONE); } else { - mAltLeftButton.setVisibility(VISIBLE); + mWalletButton.setVisibility(VISIBLE); } } @@ -703,8 +694,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void startFinishDozeAnimation() { long delay = 0; - if (mAltLeftButton.getVisibility() == View.VISIBLE) { - startFinishDozeAnimationElement(mAltLeftButton, delay); + if (mWalletButton.getVisibility() == View.VISIBLE) { + startFinishDozeAnimationElement(mWalletButton, delay); } if (mLeftAffordanceView.getVisibility() == View.VISIBLE) { startFinishDozeAnimationElement(mLeftAffordanceView, delay); @@ -778,14 +769,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL updateCameraVisibility(); updateLeftAffordanceIcon(); - updateControlsVisibility(); + updateWalletVisibility(); if (dozing) { mOverlayContainer.setVisibility(INVISIBLE); - if (mControlsDialog != null) { - mControlsDialog.dismiss(); - mControlsDialog = null; - } mEmergencyCarrierArea.setVisibility(INVISIBLE); } else { mOverlayContainer.setVisibility(VISIBLE); @@ -817,7 +804,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setAlpha(alpha); mRightAffordanceView.setAlpha(alpha); mIndicationArea.setAlpha(alpha); - mAltLeftButton.setAlpha(alpha); + mWalletButton.setAlpha(alpha); mEmergencyCarrierArea.setAlpha(alpha); } @@ -891,38 +878,18 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL return insets; } - /** - * Show or hide controls, depending on the lock screen mode and controls - * availability. - */ - public void setupControls(ControlsComponent component, BroadcastDispatcher dispatcher) { - mControlsComponent = component; - mBroadcastDispatcher = dispatcher; - setupControls(); - } - - private void setupControls() { + private void setupWallet() { boolean inNewLayout = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; boolean settingEnabled = Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 1; - if (!inNewLayout || !settingEnabled || !mControlsComponent.isEnabled()) { - mAltLeftButton.setVisibility(View.GONE); + if (!inNewLayout || !settingEnabled) { + mWalletButton.setVisibility(View.GONE); return; } - mControlsComponent.getControlsListingController().get() - .addCallback(list -> { - if (!list.isEmpty()) { - mAltLeftButton.setImageDrawable(list.get(0).loadIcon()); - mAltLeftButton.setOnClickListener((v) -> { - ControlsUiController ui = mControlsComponent - .getControlsUiController().get(); - mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher) - .show(ui); - }); - } - updateControlsVisibility(); - }); + // TODO: add image + // mWalletButton.setImageDrawable(list.get(0).loadIcon()); + updateWalletVisibility(); } /** @@ -930,6 +897,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL */ public void onLockScreenModeChanged(int mode) { mLockScreenMode = mode; - setupControls(); + setupWallet(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index a6daed5a0850..d6380199e844 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -89,7 +89,8 @@ public class KeyguardClockPositionAlgorithm { private int mNotificationStackHeight; /** - * Minimum top margin to avoid overlap with status bar. + * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher + * avatar. */ private int mMinTopMargin; @@ -186,15 +187,15 @@ public class KeyguardClockPositionAlgorithm { /** * Sets up algorithm values. */ - public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight, - float panelExpansion, int parentHeight, int keyguardStatusHeight, - int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY, - boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount, - boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon, - float qsExpansion, int cutoutTopInset) { - mMinTopMargin = statusBarMinHeight + (showLockIcon - ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon) - + userSwitchHeight; + public void setup(int keyguardStatusBarHeaderHeight, int maxShadeBottom, + int notificationStackHeight, float panelExpansion, int parentHeight, + int keyguardStatusHeight, int userSwitchHeight, int clockPreferredY, + int userSwitchPreferredY, boolean hasCustomClock, boolean hasVisibleNotifs, float dark, + float emptyDragAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, + boolean showLockIcon, float qsExpansion, int cutoutTopInset) { + mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(showLockIcon + ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon, + userSwitchHeight); mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; mPanelExpansion = panelExpansion; @@ -344,8 +345,11 @@ public class KeyguardClockPositionAlgorithm { } private float burnInPreventionOffsetX() { - return getBurnInOffset(mBurnInPreventionOffsetX * 2, true /* xAxis */) - - mBurnInPreventionOffsetX; + if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) { + return getBurnInOffset(mBurnInPreventionOffsetX * 2, true /* xAxis */) + - mBurnInPreventionOffsetX; + } + return getBurnInOffset(mBurnInPreventionOffsetX, true /* xAxis */); } public static class Result { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 9561851ab28b..89b97aef6283 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -155,7 +155,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private int mCannedAnimationStartIndex = -1; private int mSpeedBumpIndex = -1; private int mIconSize; - private float mOpenedAmount = 0.0f; private boolean mDisallowNextAnimation; private boolean mAnimationsEnabled = true; private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons; @@ -349,6 +348,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } } + public boolean hasMaxNumDot() { + return mNumDots >= MAX_DOTS; + } + private boolean areAnimationsEnabled(StatusBarIconView icon) { return mAnimationsEnabled || icon == mIsolatedIcon; } @@ -391,7 +394,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float overflowStart = getMaxOverflowStart(); mVisualOverflowStart = 0; mFirstVisibleIconState = null; - boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount(); for (int i = 0; i < childCount; i++) { View view = getChildAt(i); IconState iconState = mIconStates.get(view); @@ -410,10 +412,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float drawingScale = mOnLockScreen && view instanceof StatusBarIconView ? ((StatusBarIconView) view).getIconScaleIncreased() : 1f; - if (mOpenedAmount != 0.0f) { - noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow; - } - iconState.visibleState = StatusBarIconView.STATE_ICON; + iconState.visibleState = iconState.hidden + ? StatusBarIconView.STATE_HIDDEN + : StatusBarIconView.STATE_ICON; boolean isOverflowing = (translationX > (noOverflowAfter ? layoutEnd - mIconSize @@ -598,10 +599,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mSpeedBumpIndex = speedBumpIndex; } - public void setOpenedAmount(float expandAmount) { - mOpenedAmount = expandAmount; - } - public boolean hasOverflow() { return mNumDots > 0; } @@ -706,13 +703,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { public boolean justAdded = true; private boolean justReplaced; public boolean needsCannedAnimation; - public boolean useFullTransitionAmount; - public boolean useLinearTransitionAmount; - public boolean translateContent; public int iconColor = StatusBarIconView.NO_COLOR; public boolean noAnimations; - public boolean isLastExpandIcon; - public int customTransformHeight = NO_VALUE; private final View mView; private final Consumer<Property> mCannedAnimationEndListener; @@ -734,8 +726,15 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { StatusBarIconView icon = (StatusBarIconView) view; boolean animate = false; AnimationProperties animationProperties = null; - boolean animationsAllowed = areAnimationsEnabled(icon) && !mDisallowNextAnimation - && !noAnimations; + final boolean isLowPriorityIconChange = + (visibleState == StatusBarIconView.STATE_HIDDEN + && icon.getVisibleState() == StatusBarIconView.STATE_DOT) + || (visibleState == StatusBarIconView.STATE_DOT + && icon.getVisibleState() == StatusBarIconView.STATE_HIDDEN); + final boolean animationsAllowed = areAnimationsEnabled(icon) + && !mDisallowNextAnimation + && !noAnimations + && !isLowPriorityIconChange; if (animationsAllowed) { if (justAdded || justReplaced) { super.applyToView(icon); @@ -822,10 +821,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { needsCannedAnimation = false; } - public boolean hasCustomTransformHeight() { - return isLastExpandIcon && customTransformHeight != NO_VALUE; - } - @Override public void initFrom(View view) { super.initFrom(view); 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 0b3fd161d865..19b98953325f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -89,10 +89,8 @@ import com.android.systemui.DejankUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeLog; @@ -196,7 +194,8 @@ public class NotificationPanelViewController extends PanelViewController { new MyOnHeadsUpChangedListener(); private final HeightListener mHeightListener = new HeightListener(); private final ConfigurationListener mConfigurationListener = new ConfigurationListener(); - private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener(); + @VisibleForTesting final StatusBarStateListener mStatusBarStateListener = + new StatusBarStateListener(); private final ExpansionCallback mExpansionCallback = new ExpansionCallback(); private final BiometricUnlockController mBiometricUnlockController; private final NotificationPanelView mView; @@ -244,7 +243,6 @@ public class NotificationPanelViewController extends PanelViewController { public void onLockScreenModeChanged(int mode) { mLockScreenMode = mode; mClockPositionAlgorithm.onLockScreenModeChanged(mode); - mKeyguardBottomArea.onLockScreenModeChanged(mode); } @Override @@ -280,37 +278,12 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onKeyguardVisibilityChanged(boolean showing) { - if (mDisabledUdfpsController == null - && mAuthController.getUdfpsRegion() != null - && mAuthController.isUdfpsEnrolled( - KeyguardUpdateMonitor.getCurrentUser())) { - mLayoutInflater.inflate(R.layout.disabled_udfps_view, mView); - mDisabledUdfpsController = new DisabledUdfpsController( - mView.findViewById(R.id.disabled_udfps_view), - mStatusBarStateController, - mUpdateMonitor, - mAuthController, - mStatusBarKeyguardViewManager); - mDisabledUdfpsController.init(); + if (showing) { + updateDisabledUdfpsController(); } } }; - final KeyguardUserSwitcherController.KeyguardUserSwitcherListener - mKeyguardUserSwitcherListener = - new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() { - @Override - public void onKeyguardUserSwitcherChanged(boolean open) { - if (mKeyguardUserSwitcherController == null) { - updateUserSwitcherVisibility(false); - } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) { - updateUserSwitcherVisibility(open - && mKeyguardStateController.isShowing() - && !mKeyguardStateController.isKeyguardFadingAway()); - } - } - }; - private final LayoutInflater mLayoutInflater; private final PowerManager mPowerManager; private final AccessibilityManager mAccessibilityManager; @@ -328,7 +301,6 @@ public class NotificationPanelViewController extends PanelViewController { private final QSDetailDisplayer mQSDetailDisplayer; private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; - private final ControlsComponent mControlsComponent; // 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 @@ -338,7 +310,6 @@ public class NotificationPanelViewController extends PanelViewController { private KeyguardAffordanceHelper mAffordanceHelper; private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; - private boolean mKeyguardUserSwitcherIsShowing; private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; private ViewGroup mBigClockContainer; @@ -383,6 +354,7 @@ public class NotificationPanelViewController extends PanelViewController { private ValueAnimator mQsExpansionAnimator; private FlingAnimationUtils mFlingAnimationUtils; private int mStatusBarMinHeight; + private int mStatusBarHeaderHeightKeyguard; private int mNotificationsHeaderCollideDistance; private float mEmptyDragAmount; private float mDownX; @@ -473,7 +445,7 @@ public class NotificationPanelViewController extends PanelViewController { private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners = new ArrayList<>(); - private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>(); + private Runnable mVerticalTranslationListener; private HeadsUpAppearanceController mHeadsUpAppearanceController; private int mPanelAlpha; @@ -544,7 +516,6 @@ public class NotificationPanelViewController extends PanelViewController { private NotificationShelfController mNotificationShelfController; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; - private BroadcastDispatcher mBroadcastDispatcher; private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() { @Override @@ -602,9 +573,7 @@ public class NotificationPanelViewController extends PanelViewController { UserManager userManager, MediaDataManager mediaDataManager, AmbientState ambientState, - FeatureFlags featureFlags, - ControlsComponent controlsComponent, - BroadcastDispatcher broadcastDispatcher) { + FeatureFlags featureFlags) { super(view, falsingManager, dozeLog, keyguardStateController, (SysuiStatusBarStateController) statusBarStateController, vibratorHelper, latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager, @@ -629,6 +598,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardQsUserSwitchEnabled = mKeyguardUserSwitcherEnabled && mResources.getBoolean( R.bool.config_keyguard_user_switch_opens_qs_details); + keyguardUpdateMonitor.setKeyguardQsUserSwitchEnabled(mKeyguardQsUserSwitchEnabled); mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; @@ -646,7 +616,6 @@ public class NotificationPanelViewController extends PanelViewController { mScrimController = scrimController; mUserManager = userManager; mMediaDataManager = mediaDataManager; - mControlsComponent = controlsComponent; pulseExpansionHandler.setPulseExpandAbortListener(() -> { if (mQs != null) { mQs.animateHeaderSlidingOut(); @@ -685,7 +654,6 @@ public class NotificationPanelViewController extends PanelViewController { mEntryManager = notificationEntryManager; mConversationNotificationManager = conversationNotificationManager; mAuthController = authController; - mBroadcastDispatcher = broadcastDispatcher; mView.setBackgroundColor(Color.TRANSPARENT); OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener(); @@ -781,6 +749,8 @@ public class NotificationPanelViewController extends PanelViewController { .setMaxLengthSeconds(0.4f).build(); mStatusBarMinHeight = mResources.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); + mStatusBarHeaderHeightKeyguard = mResources.getDimensionPixelSize( + R.dimen.status_bar_header_height_keyguard); mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height); mNotificationsHeaderCollideDistance = mResources.getDimensionPixelSize( R.dimen.header_notifications_collide_distance); @@ -817,7 +787,6 @@ public class NotificationPanelViewController extends PanelViewController { // Try to close the switcher so that callbacks are triggered if necessary. // Otherwise, NPV can get into a state where some of the views are still hidden mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false); - mKeyguardUserSwitcherController.removeCallback(); } mKeyguardQsUserSwitchController = null; @@ -837,7 +806,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView); mKeyguardUserSwitcherController = userSwitcherComponent.getKeyguardUserSwitcherController(); - mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener); mKeyguardUserSwitcherController.init(); mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true); } else { @@ -867,25 +835,16 @@ public class NotificationPanelViewController extends PanelViewController { public void updateResources() { int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width); - ViewGroup.LayoutParams lp = mQsFrame.getLayoutParams(); - if (lp.width != qsWidth) { - lp.width = qsWidth; - mQsFrame.setLayoutParams(lp); - } - int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width); - lp = mNotificationStackScrollLayoutController.getLayoutParams(); - if (lp.width != panelWidth) { - lp.width = panelWidth; - mNotificationStackScrollLayoutController.setLayoutParams(lp); - } - // In order to change the constraints at runtime, all children of the Constraint Layout - // must have ids. + // To change the constraints at runtime, all children of the ConstraintLayout must have ids ensureAllViewsHaveIds(mNotificationContainerParent); ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(mNotificationContainerParent); if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { + // width = 0 to take up all available space within constraints + qsWidth = 0; + panelWidth = 0; constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END); constraintSet.connect( R.id.notification_stack_scroller, START, @@ -894,6 +853,8 @@ public class NotificationPanelViewController extends PanelViewController { constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END); constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START); } + constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth; + constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth; constraintSet.applyTo(mNotificationContainerParent); } @@ -1002,7 +963,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper); mKeyguardBottomArea.setStatusBar(mStatusBar); mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete); - mKeyguardBottomArea.setupControls(mControlsComponent, mBroadcastDispatcher); } private void updateMaxDisplayedNotifications(boolean recompute) { @@ -1085,16 +1045,15 @@ public class NotificationPanelViewController extends PanelViewController { int totalHeight = mView.getHeight(); int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding); int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight); - int userSwitcherPreferredY = mStatusBarMinHeight; + int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard; boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia(); mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications); int userIconHeight = mKeyguardQsUserSwitchController != null - ? mKeyguardQsUserSwitchController.getUserIconHeight() - : (mKeyguardUserSwitcherController != null - ? mKeyguardUserSwitcherController.getUserIconHeight() : 0); - mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding, + ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0; + mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, + totalHeight - bottomPadding, mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), getExpandedFraction(), totalHeight, @@ -1852,7 +1811,7 @@ public class NotificationPanelViewController extends PanelViewController { } } - private void setQsExpanded(boolean expanded) { + @VisibleForTesting void setQsExpanded(boolean expanded) { boolean changed = mQsExpanded != expanded; if (changed) { mQsExpanded = expanded; @@ -1955,8 +1914,10 @@ public class NotificationPanelViewController extends PanelViewController { private void updateQsState() { mNotificationStackScrollLayoutController.setQsExpanded(mQsExpanded); mNotificationStackScrollLayoutController.setScrollingEnabled( - mBarState != KEYGUARD && (!mQsExpanded - || mQsExpansionFromOverscroll)); + mBarState != KEYGUARD + && (!mQsExpanded + || mQsExpansionFromOverscroll + || Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources))); if (mKeyguardUserSwitcherController != null && mQsExpanded && !mStackScrollerOverscrolling) { @@ -2236,13 +2197,16 @@ public class NotificationPanelViewController extends PanelViewController { @Override protected boolean canCollapsePanelOnTouch() { - if (!isInSettings()) { - return mBarState == KEYGUARD - || mIsPanelCollapseOnQQS - || mNotificationStackScrollLayoutController.isScrolledToBottom(); - } else { + if (!isInSettings() && mBarState == KEYGUARD) { + return true; + } + + if (mNotificationStackScrollLayoutController.isScrolledToBottom()) { return true; } + + return !Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources) + && (isInSettings() || mIsPanelCollapseOnQQS); } @Override @@ -2920,7 +2884,8 @@ public class NotificationPanelViewController extends PanelViewController { * @param x the x-coordinate the touch event */ protected void updateHorizontalPanelPosition(float x) { - if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) { + if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth() + || Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { resetHorizontalPanelPosition(); return; } @@ -2948,9 +2913,8 @@ public class NotificationPanelViewController extends PanelViewController { protected void setHorizontalPanelTranslation(float translation) { mNotificationStackScrollLayoutController.setTranslationX(translation); mQsFrame.setTranslationX(translation); - int size = mVerticalTranslationListener.size(); - for (int i = 0; i < size; i++) { - mVerticalTranslationListener.get(i).run(); + if (mVerticalTranslationListener != null) { + mVerticalTranslationListener.run(); } } @@ -3243,12 +3207,8 @@ public class NotificationPanelViewController extends PanelViewController { mTrackingHeadsUpListeners.remove(listener); } - public void addVerticalTranslationListener(Runnable verticalTranslationListener) { - mVerticalTranslationListener.add(verticalTranslationListener); - } - - public void removeVerticalTranslationListener(Runnable verticalTranslationListener) { - mVerticalTranslationListener.remove(verticalTranslationListener); + public void setVerticalTranslationListener(Runnable verticalTranslationListener) { + mVerticalTranslationListener = verticalTranslationListener; } public void setHeadsUpAppearanceController( @@ -3538,31 +3498,22 @@ public class NotificationPanelViewController extends PanelViewController { return false; } - private void updateUserSwitcherVisibility(boolean open) { - // Do not update if previously called with the same state. - if (mKeyguardUserSwitcherIsShowing == open) { - return; - } - mKeyguardUserSwitcherIsShowing = open; - - if (open) { - animateKeyguardStatusBarOut(); - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - mBarState, - true /* keyguardFadingAway */, - true /* goingToFullShade */, - mBarState); - setKeyguardBottomAreaVisibility(mBarState, true); - mNotificationContainerParent.setVisibility(View.GONE); - } else { - animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - StatusBarState.KEYGUARD, - false, - false, - StatusBarState.SHADE_LOCKED); - setKeyguardBottomAreaVisibility(mBarState, false); - mNotificationContainerParent.setVisibility(View.VISIBLE); + private void updateDisabledUdfpsController() { + final boolean udfpsEnrolled = mAuthController.getUdfpsRegion() != null + && mAuthController.isUdfpsEnrolled( + KeyguardUpdateMonitor.getCurrentUser()); + if (mDisabledUdfpsController == null && udfpsEnrolled) { + mLayoutInflater.inflate(R.layout.disabled_udfps_view, mView); + mDisabledUdfpsController = new DisabledUdfpsController( + mView.findViewById(R.id.disabled_udfps_view), + mStatusBarStateController, + mUpdateMonitor, + mAuthController, + mStatusBarKeyguardViewManager); + mDisabledUdfpsController.init(); + } else if (mDisabledUdfpsController != null && !udfpsEnrolled) { + mDisabledUdfpsController.destroy(); + mDisabledUdfpsController = null; } } @@ -3615,6 +3566,10 @@ public class NotificationPanelViewController extends PanelViewController { NotificationStackScrollLayout.OnOverscrollTopChangedListener { @Override public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { + // When in split shade, overscroll shouldn't carry through to QS + if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { + return; + } cancelQsAnimation(); if (!mQsExpansionEnabled) { amount = 0f; @@ -3981,6 +3936,7 @@ public class NotificationPanelViewController extends PanelViewController { FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener); mStatusBarStateController.addCallback(mStatusBarStateListener); mConfigurationController.addCallback(mConfigurationListener); + updateDisabledUdfpsController(); mUpdateMonitor.registerCallback(mKeyguardUpdateCallback); // Theme might have changed between inflating this view and attaching it to the // window, so diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index e394ebc65a6b..0c9ed661925c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -18,14 +18,12 @@ package com.android.systemui.statusbar.phone; import android.app.Fragment; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; import android.view.WindowInsets; import android.widget.FrameLayout; -import androidx.annotation.DimenRes; import androidx.constraintlayout.widget.ConstraintLayout; import com.android.systemui.R; @@ -83,22 +81,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - reloadWidth(mQsFrame, R.dimen.qs_panel_width); - reloadWidth(mStackScroller, R.dimen.notification_panel_width); - } - - /** - * Loads the given width resource and sets it on the given View. - */ - private void reloadWidth(View view, @DimenRes int width) { - LayoutParams params = (LayoutParams) view.getLayoutParams(); - params.width = getResources().getDimensionPixelSize(width); - view.setLayoutParams(params); - } - - @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { mBottomPadding = insets.getStableInsetBottom(); setPadding(0, 0, 0, mBottomPadding); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index fdb7d523d562..1f1b05139d31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -27,7 +27,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; -import android.annotation.Nullable; import android.content.res.Configuration; import android.content.res.Resources; import android.os.SystemClock; @@ -1261,7 +1260,10 @@ public abstract class PanelViewController { mVelocityTracker.clear(); break; } - return false; + + // Finally, if none of the above cases applies, ensure that touches do not get handled + // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858) + return (mView.getVisibility() != View.VISIBLE); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 7b2330bdcd6e..270a0f8c5d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -124,6 +124,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump public static final float BUSY_SCRIM_ALPHA = 1f; /** + * The default scrim under the expanded bubble stack. + * This should not be lower than 0.54, otherwise we won't pass GAR. + */ + public static final float BUBBLE_SCRIM_ALPHA = 0.6f; + + /** * Scrim opacity that can have text on top. */ public static final float GAR_SCRIM_ALPHA = 0.6f; @@ -207,8 +213,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump FeatureFlags featureFlags, @Main Executor mainExecutor) { mScrimStateListener = lightBarController::setScrimState; mDefaultScrimAlpha = featureFlags.isShadeOpaque() ? BUSY_SCRIM_ALPHA : GAR_SCRIM_ALPHA; - ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(featureFlags.isShadeOpaque() - ? BUSY_SCRIM_ALPHA : GAR_SCRIM_ALPHA); + ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(BUBBLE_SCRIM_ALPHA); mBlurUtils = blurUtils; mKeyguardStateController = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index d88309cd0197..9a27ac8c6e2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2692,6 +2692,10 @@ public class StatusBar extends SystemUI implements DemoMode, return mDisplay.getRotation(); } + int getDisplayId() { + return mDisplayId; + } + public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags) { startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, @@ -2717,7 +2721,7 @@ public class StatusBar extends SystemUI implements DemoMode, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(flags); int result = ActivityManager.START_CANCELED; - ActivityOptions options = new ActivityOptions(getActivityOptions( + ActivityOptions options = new ActivityOptions(getActivityOptions(mDisplayId, null /* remoteAnimation */)); options.setDisallowEnterPictureInPictureWhileLaunching( disallowEnterPictureInPictureWhileLaunching); @@ -4363,6 +4367,7 @@ public class StatusBar extends SystemUI implements DemoMode, executeActionDismissingKeyguard(() -> { try { intent.send(null, 0, null, null, null, null, getActivityOptions( + mDisplayId, mActivityLaunchAnimator.getLaunchAnimation(associatedView, isOccluded()))); } catch (PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. @@ -4384,15 +4389,41 @@ public class StatusBar extends SystemUI implements DemoMode, mMainThreadHandler.post(runnable); } - public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) { - return getDefaultActivityOptions(animationAdapter).toBundle(); + /** + * Returns an ActivityOptions bundle created using the given parameters. + * + * @param displayId The ID of the display to launch the activity in. Typically this would be the + * display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. + */ + public static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter) { + ActivityOptions options = getDefaultActivityOptions(animationAdapter); + options.setLaunchDisplayId(displayId); + options.setCallerDisplayId(displayId); + return options.toBundle(); } - public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter, - boolean isKeyguardShowing, long eventTime) { + /** + * Returns an ActivityOptions bundle created using the given parameters. + * + * @param displayId The ID of the display to launch the activity in. Typically this would be the + * display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. + * @param isKeyguardShowing Whether keyguard is currently showing. + * @param eventTime The event time in milliseconds since boot, not including sleep. See + * {@link ActivityOptions#setSourceInfo}. + */ + public static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter, boolean isKeyguardShowing, + long eventTime) { ActivityOptions options = getDefaultActivityOptions(animationAdapter); options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime); + options.setLaunchDisplayId(displayId); + options.setCallerDisplayId(displayId); return options.toBundle(); } @@ -4532,4 +4563,8 @@ public class StatusBar extends SystemUI implements DemoMode, public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) { mExpansionChangedListeners.add(listener); } + + public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) { + mExpansionChangedListeners.remove(listener); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index b4c687d36e6c..5083e330f9a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -739,8 +739,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onThemeChanged() { + boolean wasShowing = mBouncer.isShowing(); + boolean wasScrimmed = mBouncer.isScrimmed(); + hideBouncer(true /* destroyView */); mBouncer.prepare(); + + if (wasShowing) showBouncer(wasScrimmed); } public void onKeyguardFadedAway() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 598addc68d2e..34673f2503ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -427,8 +427,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit intent.getCreatorPackage(), adapter); } long eventTime = row.getAndResetLastActionUpTime(); - Bundle options = eventTime > 0 ? getActivityOptions(adapter, - mKeyguardStateController.isShowing(), eventTime) : getActivityOptions(adapter); + Bundle options = eventTime > 0 + ? getActivityOptions( + mStatusBar.getDisplayId(), + adapter, + mKeyguardStateController.isShowing(), + eventTime) + : getActivityOptions(mStatusBar.getDisplayId(), adapter); int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, null, null, options); mMainThreadHandler.post(() -> { @@ -450,6 +455,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit int launchResult = TaskStackBuilder.create(mContext) .addNextIntentWithParentStack(intent) .startActivities(getActivityOptions( + mStatusBar.getDisplayId(), mActivityLaunchAnimator.getLaunchAnimation( row, mStatusBar.isOccluded())), new UserHandle(UserHandle.getUserId(appUid))); 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 ccda7903bdd8..56186b7ec91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -27,6 +27,8 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; +import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators; import com.android.systemui.statusbar.policy.NetworkControllerImpl; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.tuner.TunerService; @@ -143,24 +145,14 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } @Override - public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, - boolean activityIn, boolean activityOut, String description, boolean isTransient, - String statusLabel) { + public void setWifiIndicators(WifiIndicators indicators) { if (DEBUG) { - Log.d(TAG, "setWifiIndicators: " - + "enabled = " + enabled + "," - + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + "," - + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + "," - + "activityIn = " + activityIn + "," - + "activityOut = " + activityOut + "," - + "description = " + description + "," - + "isTransient = " + isTransient + "," - + "statusLabel = " + statusLabel); - } - boolean visible = statusIcon.visible && !mHideWifi; - boolean in = activityIn && mActivityEnabled && visible; - boolean out = activityOut && mActivityEnabled && visible; - mIsWifiEnabled = enabled; + Log.d(TAG, "setWifiIndicators: " + indicators); + } + boolean visible = indicators.statusIcon.visible && !mHideWifi; + boolean in = indicators.activityIn && mActivityEnabled && visible; + boolean out = indicators.activityOut && mActivityEnabled && visible; + mIsWifiEnabled = indicators.enabled; WifiIconState newState = mWifiIconState.copy(); @@ -174,10 +166,10 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba newState.resId = R.drawable.ic_qs_no_internet_available; } else { newState.visible = visible; - newState.resId = statusIcon.icon; + newState.resId = indicators.statusIcon.icon; newState.activityIn = in; newState.activityOut = out; - newState.contentDescription = statusIcon.contentDescription; + newState.contentDescription = indicators.statusIcon.contentDescription; MobileIconState first = getFirstMobileState(); newState.signalSpacerVisible = first != null && first.typeId != 0; } @@ -225,45 +217,29 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } @Override - public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, - int qsType, boolean activityIn, boolean activityOut, int volteIcon, - CharSequence typeContentDescription, - CharSequence typeContentDescriptionHtml, CharSequence description, - boolean isWide, int subId, boolean roaming, boolean showTriangle) { + public void setMobileDataIndicators(MobileDataIndicators indicators) { if (DEBUG) { - Log.d(TAG, "setMobileDataIndicators: " - + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + "," - + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + "," - + "statusType = " + statusType + "," - + "qsType = " + qsType + "," - + "activityIn = " + activityIn + "," - + "activityOut = " + activityOut + "," - + "typeContentDescription = " + typeContentDescription + "," - + "typeContentDescriptionHtml = " + typeContentDescriptionHtml + "," - + "description = " + description + "," - + "isWide = " + isWide + "," - + "subId = " + subId + "," - + "roaming = " + roaming + "," - + "showTriangle = " + showTriangle); - } - MobileIconState state = getState(subId); + Log.d(TAG, "setMobileDataIndicators: " + indicators); + } + MobileIconState state = getState(indicators.subId); if (state == null) { return; } // Visibility of the data type indicator changed - boolean typeChanged = statusType != state.typeId && (statusType == 0 || state.typeId == 0); - - state.visible = statusIcon.visible && !mHideMobile; - state.strengthId = statusIcon.icon; - state.typeId = statusType; - state.contentDescription = statusIcon.contentDescription; - state.typeContentDescription = typeContentDescription; - state.showTriangle = showTriangle; - state.roaming = roaming; - state.activityIn = activityIn && mActivityEnabled; - state.activityOut = activityOut && mActivityEnabled; - state.volteId = volteIcon; + boolean typeChanged = indicators.statusType != state.typeId + && (indicators.statusType == 0 || state.typeId == 0); + + state.visible = indicators.statusIcon.visible && !mHideMobile; + state.strengthId = indicators.statusIcon.icon; + state.typeId = indicators.statusType; + state.contentDescription = indicators.statusIcon.contentDescription; + state.typeContentDescription = indicators.typeContentDescription; + state.showTriangle = indicators.showTriangle; + state.roaming = indicators.roaming; + state.activityIn = indicators.activityIn && mActivityEnabled; + state.activityOut = indicators.activityOut && mActivityEnabled; + state.volteId = indicators.volteIcon; if (DEBUG) { Log.d(TAG, "MobileIconStates: " diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java index c1031a2b3c7b..b96cb5e36c82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java @@ -23,7 +23,9 @@ import android.telephony.SubscriptionInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators; import java.io.PrintWriter; import java.text.SimpleDateFormat; @@ -119,63 +121,29 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa } @Override - public void setWifiIndicators(final boolean enabled, final IconState statusIcon, - final IconState qsIcon, final boolean activityIn, final boolean activityOut, - final String description, boolean isTransient, String secondaryLabel) { + public void setWifiIndicators(final WifiIndicators indicators) { String log = new StringBuilder() .append(SSDF.format(System.currentTimeMillis())).append(",") - .append("setWifiIndicators: ") - .append("enabled=").append(enabled).append(",") - .append("statusIcon=").append(statusIcon).append(",") - .append("qsIcon=").append(qsIcon).append(",") - .append("activityIn=").append(activityIn).append(",") - .append("activityOut=").append(activityOut).append(",") - .append("description=").append(description).append(",") - .append("isTransient=").append(isTransient).append(",") - .append("secondaryLabel=").append(secondaryLabel) + .append(indicators) .toString(); recordLastCallback(log); post(() -> { for (SignalCallback callback : mSignalCallbacks) { - callback.setWifiIndicators(enabled, statusIcon, qsIcon, activityIn, activityOut, - description, isTransient, secondaryLabel); + callback.setWifiIndicators(indicators); } }); - - } @Override - public void setMobileDataIndicators(final IconState statusIcon, final IconState qsIcon, - final int statusType, final int qsType, final boolean activityIn, - final boolean activityOut, final int volteIcon, final CharSequence typeContentDescription, - CharSequence typeContentDescriptionHtml, final CharSequence description, - final boolean isWide, final int subId, boolean roaming, boolean showTriangle) { + public void setMobileDataIndicators(final MobileDataIndicators indicators) { String log = new StringBuilder() .append(SSDF.format(System.currentTimeMillis())).append(",") - .append("setMobileDataIndicators: ") - .append("statusIcon=").append(statusIcon).append(",") - .append("qsIcon=").append(qsIcon).append(",") - .append("statusType=").append(statusType).append(",") - .append("qsType=").append(qsType).append(",") - .append("activityIn=").append(activityIn).append(",") - .append("activityOut=").append(activityOut).append(",") - .append("typeContentDescription=").append(typeContentDescription).append(",") - .append("typeContentDescriptionHtml=").append(typeContentDescriptionHtml) - .append(",") - .append("description=").append(description).append(",") - .append("isWide=").append(isWide).append(",") - .append("subId=").append(subId).append(",") - .append("roaming=").append(roaming).append(",") - .append("showTriangle=").append(showTriangle) + .append(indicators) .toString(); recordLastCallback(log); post(() -> { for (SignalCallback signalCluster : mSignalCallbacks) { - signalCluster.setMobileDataIndicators(statusIcon, qsIcon, statusType, qsType, - activityIn, activityOut, volteIcon, typeContentDescription, - typeContentDescriptionHtml, description, isWide, subId, roaming, - showTriangle); + signalCluster.setMobileDataIndicators(indicators); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index a76d08a438f2..7f935d28285f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -16,17 +16,17 @@ package com.android.systemui.statusbar.policy; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; public interface IndividualSensorPrivacyController extends CallbackController<IndividualSensorPrivacyController.Callback> { void init(); - boolean isSensorBlocked(@IndividualSensor int sensor); + boolean isSensorBlocked(@Sensor int sensor); - void setSensorBlocked(@IndividualSensor int sensor, boolean blocked); + void setSensorBlocked(@Sensor int sensor, boolean blocked); interface Callback { - void onSensorBlockedChanged(@IndividualSensor int sensor, boolean blocked); + void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 32d15ed41648..295df05797ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.policy; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import android.hardware.SensorPrivacyManager; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.util.ArraySet; import android.util.SparseBooleanArray; @@ -30,8 +30,7 @@ import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { - private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA, - INDIVIDUAL_SENSOR_MICROPHONE}; + private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE}; private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mState = new SparseBooleanArray(); @@ -48,18 +47,18 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mSensorPrivacyManager.addSensorPrivacyListener(sensor, (enabled) -> onSensorPrivacyChanged(sensor, enabled)); - mState.put(sensor, mSensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)); + mState.put(sensor, mSensorPrivacyManager.isSensorPrivacyEnabled(sensor)); } } @Override - public boolean isSensorBlocked(@IndividualSensor int sensor) { + public boolean isSensorBlocked(@Sensor int sensor) { return mState.get(sensor, false); } @Override - public void setSensorBlocked(@IndividualSensor int sensor, boolean blocked) { - mSensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, blocked); + public void setSensorBlocked(@Sensor int sensor, boolean blocked) { + mSensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, blocked); } @Override @@ -72,7 +71,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mCallbacks.remove(listener); } - private void onSensorPrivacyChanged(@IndividualSensor int sensor, boolean blocked) { + private void onSensorPrivacyChanged(@Sensor int sensor, boolean blocked) { mState.put(sensor, blocked); for (Callback callback : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt index 4f69cd6a1367..4e5c461b9643 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * 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. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 38f3bc891394..59c1138431fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -94,15 +94,6 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie goingToFullShade, oldState); } - - @Override - public void onDozeAmountChanged(float linearAmount, float amount) { - if (DEBUG) { - Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f", - linearAmount, amount)); - } - setDarkAmount(amount); - } }; @Inject @@ -294,20 +285,6 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie } } - /** - * Set the amount (ratio) that the device has transitioned to doze. - * - * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. - */ - private void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; - if (darkAmount == mDarkAmount) { - return; - } - mDarkAmount = darkAmount; - mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); - } - private boolean isListAnimating() { return mKeyguardVisibilityHelper.isVisibilityAnimating(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index b76e451cb681..25e908450808 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -19,9 +19,13 @@ package com.android.systemui.statusbar.policy; import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; import android.database.DataSetObserver; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.UserHandle; @@ -29,7 +33,6 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardUpdateMonitor; @@ -50,7 +53,6 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.ViewController; -import java.lang.ref.WeakReference; import java.util.ArrayList; import javax.inject.Inject; @@ -73,18 +75,17 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS private final KeyguardUserAdapter mAdapter; private final KeyguardStateController mKeyguardStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback; protected final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; + private ObjectAnimator mBgAnimator; + private final KeyguardUserSwitcherScrim mBackground; // Child views of KeyguardUserSwitcherView private KeyguardUserSwitcherListView mListView; - private LinearLayout mEndGuestButton; // State info for the user switcher private boolean mUserSwitcherOpen; private int mCurrentUserId = UserHandle.USER_NULL; - private boolean mCurrentUserIsGuest; private int mBarState; private float mDarkAmount; @@ -171,6 +172,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mUserSwitcherController, this); mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters); + mBackground = new KeyguardUserSwitcherScrim(context); } @Override @@ -180,11 +182,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS if (DEBUG) Log.d(TAG, "onInit"); mListView = mView.findViewById(R.id.keyguard_user_switcher_list); - mEndGuestButton = mView.findViewById(R.id.end_guest_button); - - mEndGuestButton.setOnClickListener(v -> { - mUserSwitcherController.showExitGuestDialog(mCurrentUserId); - }); mView.setOnTouchListener((v, event) -> { if (!isListAnimating()) { @@ -200,10 +197,18 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS protected void onViewAttached() { if (DEBUG) Log.d(TAG, "onViewAttached"); mAdapter.registerDataSetObserver(mDataSetObserver); - mDataSetObserver.onChanged(); + mAdapter.notifyDataSetChanged(); mKeyguardUpdateMonitor.registerCallback(mInfoCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mScreenLifecycle.addObserver(mScreenObserver); + if (isSimpleUserSwitcher()) { + // Don't use the background for the simple user switcher + setUserSwitcherOpened(true /* open */, true /* animate */); + } else { + mView.addOnLayoutChangeListener(mBackground); + mView.setBackground(mBackground); + mBackground.setAlpha(0); + } } @Override @@ -217,6 +222,9 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mKeyguardUpdateMonitor.removeCallback(mInfoCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); mScreenLifecycle.removeObserver(mScreenObserver); + mView.removeOnLayoutChangeListener(mBackground); + mView.setBackground(null); + mBackground.setAlpha(0); } /** @@ -280,7 +288,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } foundCurrentUser = true; mCurrentUserId = userTag.info.id; - mCurrentUserIsGuest = userTag.isGuest; // Current user is always visible newView.updateVisibilities(true /* showItem */, mUserSwitcherOpen /* showTextName */, false /* animate */); @@ -306,19 +313,10 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS if (!foundCurrentUser) { Log.w(TAG, "Current user is not listed"); mCurrentUserId = UserHandle.USER_NULL; - mCurrentUserIsGuest = false; } } /** - * Get the height of the keyguard user switcher view when closed. - */ - public int getUserIconHeight() { - View firstChild = mListView.getChildAt(0); - return firstChild == null ? 0 : firstChild.getHeight(); - } - - /** * Set the visibility of the keyguard user switcher view based on some new state. */ public void setKeyguardUserSwitcherVisibility( @@ -338,6 +336,13 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS animate); PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), ANIMATION_PROPERTIES, animate); + + Rect r = new Rect(); + mListView.getDrawingRect(r); + mView.offsetDescendantRectToMyCoords(mListView, r); + mBackground.setGradientCenter( + (int) (mListView.getTranslationX() + r.left + r.width() / 2), + (int) (mListView.getTranslationY() + r.top + r.height() / 2)); } /** @@ -355,14 +360,13 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. */ private void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; + boolean isFullyDozed = darkAmount == 1; if (darkAmount == mDarkAmount) { return; } mDarkAmount = darkAmount; mListView.setDarkAmount(darkAmount); - mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); - if (!isAwake) { + if (isFullyDozed) { closeSwitcherIfOpenAndNotSimple(false); } } @@ -372,86 +376,50 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } /** - * Remove the callback if it exists. - */ - public void removeCallback() { - if (DEBUG) Log.d(TAG, "removeCallback"); - mKeyguardUserSwitcherCallback = null; - } - - /** - * Register to receive notifications about keyguard user switcher state - * (see {@link KeyguardUserSwitcherListener}. - * - * Only one callback can be used at a time. - * - * @param callback The callback to register - */ - public void setCallback(KeyguardUserSwitcherListener callback) { - if (DEBUG) Log.d(TAG, "setCallback"); - mKeyguardUserSwitcherCallback = new WeakReference<>(callback); - } - - /** - * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}. - * Switcher state is updatd before animations finish. + * NOTE: switcher state is updated before animations finish. * * @param animate true to animate transition. The user switcher state (i.e. * {@link #isUserSwitcherOpen()}) is updated before animation is finished. */ private void setUserSwitcherOpened(boolean open, boolean animate) { - boolean wasOpen = mUserSwitcherOpen; if (DEBUG) { - Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen, - open, animate)); + Log.d(TAG, + String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", + mUserSwitcherOpen, open, animate)); } mUserSwitcherOpen = open; - if (mUserSwitcherOpen != wasOpen) { - notifyUserSwitcherStateChanged(); - } updateVisibilities(animate); } private void updateVisibilities(boolean animate) { if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate)); - mEndGuestButton.animate().cancel(); - if (mUserSwitcherOpen && mCurrentUserIsGuest) { - // Show the "End guest session" button - mEndGuestButton.setVisibility(View.VISIBLE); - if (animate) { - mEndGuestButton.setAlpha(0f); - mEndGuestButton.animate() - .alpha(1f) - .setDuration(360) - .setInterpolator(Interpolators.ALPHA_IN) - .withEndAction(() -> { - mEndGuestButton.setClickable(true); - }); - } else { - mEndGuestButton.setClickable(true); - mEndGuestButton.setAlpha(1f); - } - } else { - // Hide the "End guest session" button. If it's already GONE, don't try to - // animate it or it will appear again for an instant. - mEndGuestButton.setClickable(false); - if (animate && mEndGuestButton.getVisibility() != View.GONE) { - mEndGuestButton.setVisibility(View.VISIBLE); - mEndGuestButton.setAlpha(1f); - mEndGuestButton.animate() - .alpha(0f) - .setDuration(360) - .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction(() -> { - mEndGuestButton.setVisibility(View.GONE); - mEndGuestButton.setAlpha(1f); - }); - } else { - mEndGuestButton.setVisibility(View.GONE); - mEndGuestButton.setAlpha(1f); - } + if (mBgAnimator != null) { + mBgAnimator.cancel(); } + if (mUserSwitcherOpen) { + mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255); + mBgAnimator.setDuration(400); + mBgAnimator.setInterpolator(Interpolators.ALPHA_IN); + mBgAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBgAnimator = null; + } + }); + mBgAnimator.start(); + } else { + mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0); + mBgAnimator.setDuration(400); + mBgAnimator.setInterpolator(Interpolators.ALPHA_OUT); + mBgAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBgAnimator = null; + } + }); + mBgAnimator.start(); + } mListView.updateVisibilities(mUserSwitcherOpen, animate); } @@ -459,34 +427,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS return mUserSwitcherOpen; } - private void notifyUserSwitcherStateChanged() { - if (DEBUG) { - Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b", - mUserSwitcherOpen)); - } - if (mKeyguardUserSwitcherCallback != null) { - KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get(); - if (cb != null) { - cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen); - } - } - } - - /** - * Callback for keyguard user switcher state information - */ - public interface KeyguardUserSwitcherListener { - - /** - * Called when the keyguard enters or leaves user switcher mode. This will be called - * before the animations are finished. - * - * @param open if true, keyguard is showing the user switcher or transitioning from/to user - * switcher mode. - */ - void onKeyguardUserSwitcherChanged(boolean open); - } - static class KeyguardUserAdapter extends UserSwitcherController.BaseUserAdapter implements View.OnClickListener { @@ -540,15 +480,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS return createUserDetailItemView(convertView, parent, item); } - @Override - public String getName(Context context, UserSwitcherController.UserRecord item) { - if (item.isGuest) { - return context.getString(com.android.settingslib.R.string.guest_nickname); - } else { - return super.getName(context, item); - } - } - KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) { if (!(convertView instanceof KeyguardUserDetailItemView) || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) { @@ -616,18 +547,11 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) { - if (user.isCurrent) { - // Close the switcher if tapping the current user - mKeyguardUserSwitcherController.setUserSwitcherOpened( - false /* open */, true /* animate */); - } else if (user.isSwitchToEnabled) { - if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) { - if (mCurrentUserView != null) { - mCurrentUserView.setActivated(false); - } - v.setActivated(true); - } + if (!user.isCurrent || user.isGuest) { onUserListItemClicked(user); + } else { + mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple( + true /* animate */); } } else { // If switcher is closed, tapping anywhere in the view will open it diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java index 7c82c116eb3d..a815adfc9c9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java @@ -35,20 +35,22 @@ public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout { private static final String TAG = "KeyguardUserSwitcherListView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; - private static final int ANIMATION_DURATION_OPENING = 360; - private static final int ANIMATION_DURATION_CLOSING = 240; - private boolean mAnimating; private final AppearAnimationUtils mAppearAnimationUtils; private final DisappearAnimationUtils mDisappearAnimationUtils; public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) { super(context, attrs); - setClipChildren(false); - mAppearAnimationUtils = new AppearAnimationUtils(context, ANIMATION_DURATION_OPENING, - -0.5f, 0.5f, Interpolators.FAST_OUT_SLOW_IN); - mDisappearAnimationUtils = new DisappearAnimationUtils(context, ANIMATION_DURATION_CLOSING, - 0.5f, 0.5f, Interpolators.FAST_OUT_LINEAR_IN); + mAppearAnimationUtils = new AppearAnimationUtils(context, + AppearAnimationUtils.DEFAULT_APPEAR_DURATION, + -0.5f /* translationScaleFactor */, + 0.5f /* delayScaleFactor */, + Interpolators.FAST_OUT_SLOW_IN); + mDisappearAnimationUtils = new DisappearAnimationUtils(context, + AppearAnimationUtils.DEFAULT_APPEAR_DURATION, + 0.2f /* translationScaleFactor */, + 0.2f /* delayScaleFactor */, + Interpolators.FAST_OUT_SLOW_IN_REVERSE); } /** @@ -82,69 +84,40 @@ public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout { mAnimating = false; - int userListCount = getChildCount(); - if (userListCount > 0) { - // The first child is always the current user. - KeyguardUserDetailItemView currentUserView = ((KeyguardUserDetailItemView) getChildAt( - 0)); - currentUserView.updateVisibilities(true /* showItem */, open /* showTextName */, - animate); - currentUserView.setClickable(true); - currentUserView.clearAnimation(); - } - - if (userListCount <= 1) { - return; - } - - if (animate) { - // Create an array of all the remaining users (that aren't the current user). - KeyguardUserDetailItemView[] otherUserViews = - new KeyguardUserDetailItemView[userListCount - 1]; - for (int i = 1, n = 0; i < userListCount; i++, n++) { - otherUserViews[n] = (KeyguardUserDetailItemView) getChildAt(i); - + int childCount = getChildCount(); + KeyguardUserDetailItemView[] userItemViews = new KeyguardUserDetailItemView[childCount]; + for (int i = 0; i < childCount; i++) { + userItemViews[i] = (KeyguardUserDetailItemView) getChildAt(i); + userItemViews[i].clearAnimation(); + if (i == 0) { + // The first child is always the current user. + userItemViews[i].updateVisibilities(true /* showItem */, open /* showTextName */, + animate); + userItemViews[i].setClickable(true); + } else { // Update clickable state immediately so that the menu feels more responsive - otherUserViews[n].setClickable(open); - + userItemViews[i].setClickable(open); // Before running the animation, ensure visibility is set correctly - otherUserViews[n].updateVisibilities( - true /* showItem */, true /* showTextName */, false /* animate */); - otherUserViews[n].clearAnimation(); + userItemViews[i].updateVisibilities(animate || open /* showItem */, + true /* showTextName */, false /* animate */); } + } + + if (animate) { + // AnimationUtils will immediately hide/show the first item in the array. Since the + // first view is the current user, we want to manage its visibility separately. + // Set first item to null so AnimationUtils ignores it. + userItemViews[0] = null; setClipChildren(false); setClipToPadding(false); - mAnimating = true; - - final int nonCurrentUserCount = otherUserViews.length; - if (open) { - mAppearAnimationUtils.startAnimation(otherUserViews, () -> { - setClipChildren(true); - setClipToPadding(true); - mAnimating = false; - }); - } else { - mDisappearAnimationUtils.startAnimation(otherUserViews, () -> { - setClipChildren(true); - setClipToPadding(true); - for (int i = 0; i < nonCurrentUserCount; i++) { - otherUserViews[i].updateVisibilities( - false /* showItem */, true /* showTextName */, false /* animate */); - } - mAnimating = false; - }); - } - } else { - for (int i = 1; i < userListCount; i++) { - KeyguardUserDetailItemView nonCurrentUserView = - ((KeyguardUserDetailItemView) getChildAt(i)); - nonCurrentUserView.clearAnimation(); - nonCurrentUserView.updateVisibilities( - open /* showItem */, true /* showTextName */, false /* animate */); - nonCurrentUserView.setClickable(open); - } + (open ? mAppearAnimationUtils : mDisappearAnimationUtils) + .startAnimation(userItemViews, () -> { + setClipChildren(true); + setClipToPadding(true); + mAnimating = false; + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java index 49f5bcdd5a44..1d9d33d2aab1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java @@ -26,7 +26,6 @@ import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Drawable; -import android.util.LayoutDirection; import android.view.View; import com.android.systemui.R; @@ -38,13 +37,14 @@ public class KeyguardUserSwitcherScrim extends Drawable implements View.OnLayoutChangeListener { private static final float OUTER_EXTENT = 2.5f; - private static final float INNER_EXTENT = 0.75f; + private static final float INNER_EXTENT = 0.25f; private int mDarkColor; - private int mTop; private int mAlpha = 255; private Paint mRadialGradientPaint = new Paint(); - private int mLayoutWidth; + private int mCircleX; + private int mCircleY; + private int mSize; public KeyguardUserSwitcherScrim(Context context) { mDarkColor = context.getColor( @@ -53,14 +53,11 @@ public class KeyguardUserSwitcherScrim extends Drawable @Override public void draw(Canvas canvas) { - boolean isLtr = getLayoutDirection() == LayoutDirection.LTR; + if (mAlpha == 0) { + return; + } Rect bounds = getBounds(); - float width = bounds.width() * OUTER_EXTENT; - float height = (mTop + bounds.height()) * OUTER_EXTENT; - canvas.translate(0, -mTop); - canvas.scale(1, height / width); - canvas.drawRect(isLtr ? bounds.right - width : 0, 0, - isLtr ? bounds.right : bounds.left + width, width, mRadialGradientPaint); + canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.bottom, mRadialGradientPaint); } @Override @@ -88,24 +85,36 @@ public class KeyguardUserSwitcherScrim extends Drawable public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { - mLayoutWidth = right - left; - mTop = top; + int width = right - left; + int height = bottom - top; + mSize = Math.max(width, height); updatePaint(); } } private void updatePaint() { - if (mLayoutWidth == 0) { + if (mSize == 0) { return; } - float radius = mLayoutWidth * OUTER_EXTENT; - boolean isLtr = getLayoutDirection() == LayoutDirection.LTR; + float outerRadius = mSize * OUTER_EXTENT; mRadialGradientPaint.setShader( - new RadialGradient(isLtr ? mLayoutWidth : 0, 0, radius, + new RadialGradient(mCircleX, mCircleY, outerRadius, new int[] { Color.argb( (int) (Color.alpha(mDarkColor) * mAlpha / 255f), 0, 0, 0), Color.TRANSPARENT }, - new float[] { Math.max(0f, mLayoutWidth * INNER_EXTENT / radius), 1f }, + new float[] { Math.max(0f, INNER_EXTENT / OUTER_EXTENT), 1f }, Shader.TileMode.CLAMP)); } + + /** + * Sets the center of the radial gradient used as a background + * + * @param x + * @param y + */ + public void setGradientCenter(int x, int y) { + mCircleX = x; + mCircleY = y; + updatePaint(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index 8fcd25042c5c..ea55da97a215 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -76,6 +76,7 @@ import com.android.systemui.statusbar.policy.FiveGServiceClient; import com.android.systemui.statusbar.policy.FiveGServiceClient.FiveGServiceState; import com.android.systemui.statusbar.policy.FiveGServiceClient.IFiveGStateListener; import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import java.io.PrintWriter; @@ -513,8 +514,15 @@ public class MobileSignalController extends SignalController<MobileState, Mobile int qsTypeIcon = 0; IconState qsIcon = null; CharSequence description = null; + // Mobile icon will only be shown in the statusbar in 2 scenarios + // 1. Mobile is the default network, and it is validated + // 2. Mobile is the default network, it is not validated and there is no other + // non-Carrier WiFi networks available. + boolean maybeShowIcons = (mCurrentState.inetCondition == 1) + || (mCurrentState.inetCondition == 0 + && !mNetworkController.isNonCarrierWifiNetworkAvailable()); // Only send data sim callbacks to QS. - if (mCurrentState.dataSim && mCurrentState.isDefault) { + if (mCurrentState.dataSim && mCurrentState.isDefault && maybeShowIcons) { qsTypeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0; qsIcon = new IconState(mCurrentState.enabled @@ -527,17 +535,19 @@ public class MobileSignalController extends SignalController<MobileState, Mobile boolean activityOut = mCurrentState.dataConnected && !mCurrentState.carrierNetworkChangeMode && mCurrentState.activityOut; - showDataIcon &= mCurrentState.dataSim && mCurrentState.isDefault; + showDataIcon &= mCurrentState.dataSim && mCurrentState.isDefault && maybeShowIcons; boolean showTriangle = showDataIcon && !mCurrentState.airplaneMode; int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0; showDataIcon |= mCurrentState.roaming; IconState statusIcon = new IconState(showDataIcon && !mCurrentState.airplaneMode, getCurrentIconId(), contentDescription); int volteIcon = mConfig.showVolteIcon && isVolteSwitchOn() ? getVolteResId() : 0; - callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon, + MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( + statusIcon, qsIcon, typeIcon, qsTypeIcon, activityIn, activityOut, volteIcon, dataContentDescription, dataContentDescriptionHtml, description, icons.isWide, mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming, showTriangle); + callback.setMobileDataIndicators(mobileDataIndicators); } else { boolean showDataIcon = mCurrentState.dataConnected || dataDisabled; IconState statusIcon = new IconState( @@ -569,7 +579,6 @@ public class MobileSignalController extends SignalController<MobileState, Mobile }else if ( mConfig.enableDdsRatIconEnhancement ) { typeIcon = getEnhancementDdsRatIcon(); } - int volteIcon = mConfig.showVolteIcon && isVolteSwitchOn() ? getVolteResId() : 0; MobileIconGroup vowifiIconGroup = getVowifiIconGroup(); if ( mConfig.showVowifiIcon && vowifiIconGroup != null ) { typeIcon = vowifiIconGroup.dataType; @@ -577,6 +586,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mCurrentState.enabled && !mCurrentState.airplaneMode? statusIcon.icon : -1, statusIcon.contentDescription); } + int volteIcon = mConfig.showVolteIcon && isVolteSwitchOn() ? getVolteResId() : 0; if (DEBUG) { Log.d(mTag, "notifyListeners mConfig.alwaysShowNetworkTypeIcon=" + mConfig.alwaysShowNetworkTypeIcon + " getNetworkType:" + mTelephonyDisplayInfo.getNetworkType() + @@ -592,10 +602,12 @@ public class MobileSignalController extends SignalController<MobileState, Mobile + " mConfig.showVowifiIcon=" + mConfig.showVowifiIcon); } boolean showTriangle = mCurrentState.enabled && !mCurrentState.airplaneMode; - callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon, + MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( + statusIcon, qsIcon, typeIcon, qsTypeIcon, activityIn, activityOut, volteIcon, dataContentDescription, dataContentDescriptionHtml, description, icons.isWide, mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming, showTriangle); + callback.setMobileDataIndicators(mobileDataIndicators); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index e93d6400dac5..ed08980f109c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -46,34 +46,118 @@ public interface NetworkController extends CallbackController<SignalCallback>, D boolean isRadioOn(); + /** + * Wrapper class for all the WiFi signals used for WiFi indicators. + */ + final class WifiIndicators { + public boolean enabled; + public IconState statusIcon; + public IconState qsIcon; + public boolean activityIn; + public boolean activityOut; + public String description; + public boolean isTransient; + public String statusLabel; + + public WifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, + boolean activityIn, boolean activityOut, String description, + boolean isTransient, String statusLabel) { + this.enabled = enabled; + this.statusIcon = statusIcon; + this.qsIcon = qsIcon; + this.activityIn = activityIn; + this.activityOut = activityOut; + this.description = description; + this.isTransient = isTransient; + this.statusLabel = statusLabel; + } + + @Override + public String toString() { + return new StringBuilder("WifiIndicators[") + .append("enabled=").append(enabled) + .append(",statusIcon=").append(statusIcon == null ? "" : statusIcon.toString()) + .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString()) + .append(",activityIn=").append(activityIn) + .append(",activityOut=").append(activityOut) + .append(",description=").append(description) + .append(",isTransient=").append(isTransient) + .append(",statusLabel=").append(statusLabel) + .append(']').toString(); + } + } + + /** + * Wrapper class for all the mobile signals used for mobile data indicators. + */ + final class MobileDataIndicators { + public IconState statusIcon; + public IconState qsIcon; + public int statusType; + public int qsType; + public boolean activityIn; + public boolean activityOut; + public int volteIcon; + public CharSequence typeContentDescription; + public CharSequence typeContentDescriptionHtml; + public CharSequence description; + public boolean isWide; + public int subId; + public boolean roaming; + public boolean showTriangle; + + public MobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, + int qsType, boolean activityIn, boolean activityOut, int volteIcon, + CharSequence typeContentDescription, CharSequence typeContentDescriptionHtml, + CharSequence description, boolean isWide, int subId, boolean roaming, + boolean showTriangle) { + this.statusIcon = statusIcon; + this.qsIcon = qsIcon; + this.statusType = statusType; + this.qsType = qsType; + this.activityIn = activityIn; + this.activityOut = activityOut; + this.typeContentDescription = typeContentDescription; + this.typeContentDescriptionHtml = typeContentDescriptionHtml; + this.description = description; + this.isWide = isWide; + this.subId = subId; + this.roaming = roaming; + this.showTriangle = showTriangle; + } + + @Override + public String toString() { + return new StringBuilder("MobileDataIndicators[") + .append("statusIcon=").append(statusIcon == null ? "" : statusIcon.toString()) + .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString()) + .append(",statusType=").append(statusType) + .append(",qsType=").append(qsType) + .append(",activityIn=").append(activityIn) + .append(",activityOut=").append(activityOut) + .append(",typeContentDescription=").append(typeContentDescription) + .append(",typeContentDescriptionHtml=").append(typeContentDescriptionHtml) + .append(",description=").append(description) + .append(",isWide=").append(isWide) + .append(",subId=").append(subId) + .append(",roaming=").append(roaming) + .append(",showTriangle=").append(showTriangle) + .append(']').toString(); + } + } + public interface SignalCallback { - default void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, - boolean activityIn, boolean activityOut, String description, boolean isTransient, - String statusLabel) {} + /** + * Callback for listeners to be able to update the state of any UI tracking connectivity of + * WiFi networks. + */ + default void setWifiIndicators(WifiIndicators wifiIndicators) {} /** * Callback for listeners to be able to update the state of any UI tracking connectivity - * @param statusIcon the icon that should be shown in the status bar - * @param qsIcon the icon to show in Quick Settings - * @param statusType the resId of the data type icon (e.g. LTE) to show in the status bar - * @param qsType similar to above, the resId of the data type icon to show in Quick Settings - * @param activityIn indicates whether there is inbound activity - * @param activityOut indicates outbound activity - * @param typeContentDescription the contentDescription of the data type - * @param typeContentDescriptionHtml the (possibly HTML-styled) contentDescription of the - * data type. Suitable for display - * @param description description of the network (usually just the network name) - * @param isWide //TODO: unused? - * @param subId subscription ID for which to update the UI - * @param roaming indicates roaming - * @param showTriangle whether to show the mobile triangle the in status bar + * of Mobile networks. */ - default void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, - int qsType, boolean activityIn, boolean activityOut, int volteIcon, - CharSequence typeContentDescription, - CharSequence typeContentDescriptionHtml, CharSequence description, - boolean isWide, int subId, boolean roaming, boolean showTriangle) { - } + default void setMobileDataIndicators(MobileDataIndicators mobileDataIndicators) {} default void setSubs(List<SubscriptionInfo> subs) {} 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 94bcc9e5be70..bb91105a13ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -167,7 +167,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private int mCurrentUserId; private OnSubscriptionsChangedListener mSubscriptionListener; - + private NetworkCapabilities mLastDefaultNetworkCapabilities; // Handler that all broadcasts are received on. private final Handler mReceiverHandler; // Handler that all callbacks are made on. @@ -322,6 +322,7 @@ public class NetworkControllerImpl extends BroadcastReceiver public void onLost(Network network) { mLastNetwork = null; mLastNetworkCapabilities = null; + mLastDefaultNetworkCapabilities = null; String callback = new StringBuilder() .append(SSDF.format(System.currentTimeMillis())).append(",") .append("onLost: ") @@ -348,6 +349,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } mLastNetwork = network; mLastNetworkCapabilities = networkCapabilities; + mLastDefaultNetworkCapabilities = networkCapabilities; String callback = new StringBuilder() .append(SSDF.format(System.currentTimeMillis())).append(",") .append("onCapabilitiesChanged: ") @@ -555,6 +557,10 @@ public class NetworkControllerImpl extends BroadcastReceiver return mWifiSignalController.isCarrierMergedWifi(subId); } + boolean isNonCarrierWifiNetworkAvailable() { + return !mNoNetworksAvailable; + } + boolean isEthernetDefault() { return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET); } @@ -918,6 +924,11 @@ public class NetworkControllerImpl extends BroadcastReceiver return true; } + @VisibleForTesting + void setNoNetworksAvailable(boolean noNetworksAvailable) { + mNoNetworksAvailable = noNetworksAvailable; + } + private void updateAirplaneMode(boolean force) { boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 1); @@ -971,18 +982,17 @@ public class NetworkControllerImpl extends BroadcastReceiver private void updateConnectivity() { mConnectedTransports.clear(); mValidatedTransports.clear(); - for (NetworkCapabilities nc : - mConnectivityManager.getDefaultNetworkCapabilitiesForUser(mCurrentUserId)) { - for (int transportType : nc.getTransportTypes()) { + if (mLastDefaultNetworkCapabilities != null) { + for (int transportType : mLastDefaultNetworkCapabilities.getTransportTypes()) { if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR - && Utils.tryGetWifiInfoForVcn(nc) != null) { + && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) { mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI); - if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) { + if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { mValidatedTransports.set(NetworkCapabilities.TRANSPORT_WIFI); } } else { mConnectedTransports.set(transportType); - if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) { + if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { mValidatedTransports.set(transportType); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 738cab15431a..5638503be198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -439,7 +439,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onAvailable(Network network) { - if (DEBUG) Log.d(TAG, "onAvailable " + network.netId); + if (DEBUG) Log.d(TAG, "onAvailable " + network.getNetId()); updateState(); fireCallbacks(); }; @@ -448,7 +448,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi // how long the VPN connection is held on to. @Override public void onLost(Network network) { - if (DEBUG) Log.d(TAG, "onLost " + network.netId); + if (DEBUG) Log.d(TAG, "onLost " + network.getNetId()); updateState(); fireCallbacks(); }; 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 ad4fa64ac905..edec61832c8e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -92,6 +92,7 @@ public class SmartReplyView extends ViewGroup { @ColorInt private int mCurrentStrokeColor; @ColorInt private int mCurrentTextColor; @ColorInt private int mCurrentRippleColor; + private boolean mCurrentColorized; private int mMaxSqueezeRemeasureAttempts; private int mMaxNumActions; private int mMinNumSystemGeneratedReplies; @@ -143,7 +144,7 @@ public class SmartReplyView extends ViewGroup { mBreakIterator = BreakIterator.getLineInstance(); - setBackgroundTintColor(mDefaultBackgroundColor); + setBackgroundTintColor(mDefaultBackgroundColor, false /* colorized */); reallocateCandidateButtonQueueForSqueezing(); } @@ -182,7 +183,7 @@ public class SmartReplyView extends ViewGroup { public void resetSmartSuggestions(View newSmartReplyContainer) { mSmartReplyContainer = newSmartReplyContainer; removeAllViews(); - setBackgroundTintColor(mDefaultBackgroundColor); + setBackgroundTintColor(mDefaultBackgroundColor, false /* colorized */); } /** Add buttons to the {@link SmartReplyView} */ @@ -676,19 +677,24 @@ public class SmartReplyView extends ViewGroup { return lp.show && super.drawChild(canvas, child, drawingTime); } - public void setBackgroundTintColor(int backgroundColor) { - if (backgroundColor == mCurrentBackgroundColor) { + /** + * Set the current background color of the notification so that the smart reply buttons can + * match it, and calculate other colors (e.g. text, ripple, stroke) + */ + public void setBackgroundTintColor(int backgroundColor, boolean colorized) { + if (backgroundColor == mCurrentBackgroundColor && colorized == mCurrentColorized) { // Same color ignoring. return; } mCurrentBackgroundColor = backgroundColor; + mCurrentColorized = colorized; final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor); mCurrentTextColor = ContrastColorUtil.ensureTextContrast( dark ? mDefaultTextColorDarkBg : mDefaultTextColor, backgroundColor | 0xff000000, dark); - mCurrentStrokeColor = ContrastColorUtil.ensureContrast( + mCurrentStrokeColor = colorized ? mCurrentTextColor : ContrastColorUtil.ensureContrast( mDefaultStrokeColor, backgroundColor | 0xff000000, dark, mMinStrokeContrast); mCurrentRippleColor = dark ? mRippleColorDarkBg : mRippleColor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index 08ca016777c8..6cb33f0ff64d 100755 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -39,7 +39,9 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators; import java.io.PrintWriter; import java.util.Objects; @@ -159,27 +161,43 @@ public class WifiSignalController extends if (mCurrentState.inetCondition == 0) { contentDescription += ("," + mContext.getString(R.string.data_connection_no_internet)); } - IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription); if (mProviderModel) { + // WiFi icon will only be shown in the statusbar in 2 scenarios + // 1. WiFi is the default network, and it is validated + // 2. WiFi is the default network, it is not validated and there is no other + // non-Carrier WiFi networks available. + boolean maybeShowIcons = (mCurrentState.inetCondition == 1) + || (mCurrentState.inetCondition == 0 + && !mNetworkController.isNonCarrierWifiNetworkAvailable()); + IconState statusIcon = new IconState( + wifiVisible && maybeShowIcons, getCurrentIconId(), contentDescription); IconState qsIcon = null; - if (mCurrentState.isDefault || (!mNetworkController.isRadioOn() + if ((mCurrentState.isDefault && maybeShowIcons) || (!mNetworkController.isRadioOn() && !mNetworkController.isEthernetDefault())) { qsIcon = new IconState(mCurrentState.connected, mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected : getQsCurrentIconId(), contentDescription); } - callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon, + WifiIndicators wifiIndicators = new WifiIndicators( + mCurrentState.enabled, statusIcon, qsIcon, ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut, - wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel); + wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel + ); + callback.setWifiIndicators(wifiIndicators); } else { + IconState statusIcon = new IconState( + wifiVisible, getCurrentIconId(), contentDescription); IconState qsIcon = new IconState(mCurrentState.connected, mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected : getQsCurrentIconId(), contentDescription); - callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon, + WifiIndicators wifiIndicators = new WifiIndicators( + mCurrentState.enabled, statusIcon, qsIcon, ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut, - wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel); + wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel + ); + callback.setWifiIndicators(wifiIndicators); } } @@ -193,23 +211,36 @@ public class WifiSignalController extends if (mCurrentState.inetCondition == 0) { dataContentDescription = mContext.getString(R.string.data_connection_no_internet); } - boolean qsVisible = mCurrentState.enabled - && (mCurrentState.connected && mCurrentState.inetCondition == 1); - + // Mobile icon will only be shown in the statusbar in 2 scenarios + // 1. Mobile is the default network, and it is validated + // 2. Mobile is the default network, it is not validated and there is no other + // non-Carrier WiFi networks available. + boolean maybeShowIcons = (mCurrentState.inetCondition == 1) + || (mCurrentState.inetCondition == 0 + && !mNetworkController.isNonCarrierWifiNetworkAvailable()); + boolean sbVisible = mCurrentState.enabled && mCurrentState.connected + && maybeShowIcons && mCurrentState.isDefault; IconState statusIcon = - new IconState(qsVisible, getCurrentIconIdForCarrierWifi(), contentDescription); - int qsTypeIcon = mCurrentState.connected ? icons.qsDataType : 0; - int typeIcon = mCurrentState.connected ? icons.dataType : 0; + new IconState(sbVisible, getCurrentIconIdForCarrierWifi(), contentDescription); + int typeIcon = sbVisible ? icons.dataType : 0; + int qsTypeIcon = 0; // TODO(b/178561525) Populate volteIcon value as necessary int volteIcon = 0; - IconState qsIcon = new IconState( - mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), contentDescription); + IconState qsIcon = null; + if (sbVisible) { + qsTypeIcon = icons.qsDataType; + qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), + contentDescription); + } CharSequence description = mNetworkController.getNetworkNameForCarrierWiFi(mCurrentState.subId); - callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon, + MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( + statusIcon, qsIcon, typeIcon, qsTypeIcon, mCurrentState.activityIn, mCurrentState.activityOut, volteIcon, dataContentDescription, dataContentDescriptionHtml, description, icons.isWide, - mCurrentState.subId, /* roaming= */ false, /* showTriangle= */ true); + mCurrentState.subId, /* roaming= */ false, /* showTriangle= */ true + ); + callback.setMobileDataIndicators(mobileDataIndicators); } private int getCurrentIconIdForCarrierWifi() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index bdf2b0c24ba5..37a763b740e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -22,12 +22,10 @@ import android.os.RemoteException; import android.os.ServiceManager; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar; import javax.inject.Inject; @@ -36,11 +34,6 @@ import dagger.Lazy; /** * Status bar implementation for "large screen" products that mostly present no on-screen nav. * Serves as a collection of UI components, rather than showing its own UI. - * The following is the list of elements that constitute the TV-specific status bar: - * <ul> - * <li> {@link AudioRecordingDisclosureBar} - shown whenever applications are conducting audio - * recording, discloses the responsible applications </li> - * </ul> */ @SysUISingleton public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks { @@ -66,11 +59,6 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks { } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. } - - if (mContext.getResources().getBoolean(R.bool.audio_recording_disclosure_enabled)) { - // Creating AudioRecordingDisclosureBar and just letting it run - new AudioRecordingDisclosureBar(mContext); - } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java deleted file mode 100644 index bbab6253a4d1..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.tv.micdisclosure; - -import android.content.Context; - -import java.util.Set; - -/** - * A base class for implementing observers for different kinds of activities related to audio - * recording. These observers are to be initialized by {@link AudioRecordingDisclosureBar} and to - * report back to it. - */ -abstract class AudioActivityObserver { - - interface OnAudioActivityStateChangeListener { - void onAudioActivityStateChange(boolean active, String packageName); - } - - final Context mContext; - - final OnAudioActivityStateChangeListener mListener; - - AudioActivityObserver(Context context, OnAudioActivityStateChangeListener listener) { - mContext = context; - mListener = listener; - } - - abstract void start(); - - abstract void stop(); - - abstract Set<String> getActivePackages(); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java deleted file mode 100644 index 8caf95fb48f5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.tv.micdisclosure; - -import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; - -import static com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar.DEBUG; - -import android.annotation.UiThread; -import android.app.ActivityManager; -import android.app.IActivityManager; -import android.app.IProcessObserver; -import android.content.Context; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseArray; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * The purpose of these class is to detect packages that are running foreground services of type - * 'microphone' and to report back to {@link AudioRecordingDisclosureBar}. - */ -class MicrophoneForegroundServicesObserver extends AudioActivityObserver { - private static final String TAG = "MicrophoneForegroundServicesObserver"; - - private IActivityManager mActivityManager; - /** - * A dictionary that maps PIDs to the package names. We only keep track of the PIDs that are - * "active" (those that are running FGS with FOREGROUND_SERVICE_TYPE_MICROPHONE flag). - */ - private final SparseArray<String[]> mPidToPackages = new SparseArray<>(); - /** - * A dictionary that maps "active" packages to the number of the "active" processes associated - * with those packages. We really only need this in case when one application is running in - * multiple processes, so that we don't lose track of the package when one of its "active" - * processes ceases, while others remain "active". - */ - private final Map<String, Integer> mPackageToProcessCount = new ArrayMap<>(); - - MicrophoneForegroundServicesObserver(Context context, - OnAudioActivityStateChangeListener listener) { - super(context, listener); - } - - @Override - void start() { - mActivityManager = ActivityManager.getService(); - try { - mActivityManager.registerProcessObserver(mProcessObserver); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't register process observer", e); - } - } - - @Override - void stop() { - try { - mActivityManager.unregisterProcessObserver(mProcessObserver); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't unregister process observer", e); - } - mActivityManager = null; - mPackageToProcessCount.clear(); - } - - @Override - Set<String> getActivePackages() { - return mPackageToProcessCount.keySet(); - } - - @UiThread - private void onProcessForegroundServicesChanged(int pid, boolean hasMicFgs) { - final String[] changedPackages; - if (hasMicFgs) { - if (mPidToPackages.contains(pid)) { - // We are already tracking this pid - ignore. - changedPackages = null; - } else { - changedPackages = getPackageNames(pid); - mPidToPackages.append(pid, changedPackages); - } - } else { - changedPackages = mPidToPackages.removeReturnOld(pid); - } - - if (changedPackages == null) { - return; - } - - for (int index = changedPackages.length - 1; index >= 0; index--) { - final String packageName = changedPackages[index]; - int processCount = mPackageToProcessCount.getOrDefault(packageName, 0); - final boolean shouldNotify; - if (hasMicFgs) { - processCount++; - shouldNotify = processCount == 1; - } else { - processCount--; - shouldNotify = processCount == 0; - } - if (processCount > 0) { - mPackageToProcessCount.put(packageName, processCount); - } else { - mPackageToProcessCount.remove(packageName); - } - if (shouldNotify) notifyPackageStateChanged(packageName, hasMicFgs); - } - } - - @UiThread - private void onProcessDied(int pid) { - final String[] packages = mPidToPackages.removeReturnOld(pid); - if (packages == null) { - // This PID was not active - ignore. - return; - } - - for (int index = packages.length - 1; index >= 0; index--) { - final String packageName = packages[index]; - int processCount = mPackageToProcessCount.getOrDefault(packageName, 0); - if (processCount <= 0) { - Log.e(TAG, "Bookkeeping error, process count for " + packageName + " is " - + processCount); - continue; - } - processCount--; - if (processCount > 0) { - mPackageToProcessCount.put(packageName, processCount); - } else { - mPackageToProcessCount.remove(packageName); - notifyPackageStateChanged(packageName, false); - } - } - } - - @UiThread - private void notifyPackageStateChanged(String packageName, boolean active) { - if (DEBUG) { - Log.d(TAG, (active ? "New microphone fgs detected" : "Microphone fgs is gone") - + ", package=" + packageName); - } - - mListener.onAudioActivityStateChange(active, packageName); - } - - @UiThread - private String[] getPackageNames(int pid) { - final List<ActivityManager.RunningAppProcessInfo> runningApps; - try { - runningApps = mActivityManager.getRunningAppProcesses(); - } catch (RemoteException e) { - Log.d(TAG, "Couldn't get package name for pid=" + pid); - return null; - } - if (runningApps == null) { - Log.wtf(TAG, "No running apps reported"); - } - for (ActivityManager.RunningAppProcessInfo app : runningApps) { - if (app.pid == pid) { - return app.pkgList; - } - } - return null; - } - - private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() { - @Override - public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {} - - @Override - public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { - mContext.getMainExecutor().execute(() -> onProcessForegroundServicesChanged(pid, - (serviceTypes & FOREGROUND_SERVICE_TYPE_MICROPHONE) != 0)); - } - - @Override - public void onProcessDied(int pid, int uid) { - mContext.getMainExecutor().execute( - () -> MicrophoneForegroundServicesObserver.this.onProcessDied(pid)); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java deleted file mode 100644 index 9a2b4a93ac89..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.tv.micdisclosure; - -import static com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar.DEBUG; - -import android.annotation.UiThread; -import android.app.AppOpsManager; -import android.content.Context; -import android.util.ArraySet; -import android.util.Log; - -import java.util.Set; - -/** - * The purpose of these class is to detect packages that are conducting audio recording (according - * to {@link AppOpsManager}) and report this to {@link AudioRecordingDisclosureBar}. - */ -class RecordAudioAppOpObserver extends AudioActivityObserver implements - AppOpsManager.OnOpActiveChangedListener { - private static final String TAG = "RecordAudioAppOpObserver"; - - /** - * Set of the applications that currently are conducting audio recording according to {@link - * AppOpsManager}. - */ - private final Set<String> mActiveAudioRecordingPackages = new ArraySet<>(); - - RecordAudioAppOpObserver(Context context, OnAudioActivityStateChangeListener listener) { - super(context, listener); - } - - @Override - void start() { - if (DEBUG) { - Log.d(TAG, "Start"); - } - - // Register AppOpsManager callback - mContext.getSystemService(AppOpsManager.class) - .startWatchingActive( - new String[]{AppOpsManager.OPSTR_RECORD_AUDIO}, - mContext.getMainExecutor(), - this); - } - - @Override - void stop() { - if (DEBUG) { - Log.d(TAG, "Stop"); - } - - // Unregister AppOpsManager callback - mContext.getSystemService(AppOpsManager.class).stopWatchingActive(this); - - // Clean up state - mActiveAudioRecordingPackages.clear(); - } - - @UiThread - @Override - Set<String> getActivePackages() { - return mActiveAudioRecordingPackages; - } - - @UiThread - @Override - public void onOpActiveChanged(String op, int uid, String packageName, boolean active) { - if (DEBUG) { - Log.d(TAG, - "OP_RECORD_AUDIO active change, active=" + active + ", package=" - + packageName); - } - - if (active) { - if (mActiveAudioRecordingPackages.add(packageName)) { - mListener.onAudioActivityStateChange(true, packageName); - } - } else { - if (mActiveAudioRecordingPackages.remove(packageName)) { - mListener.onAudioActivityStateChange(false, packageName); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java index 30f401b91d25..b325b10957f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java @@ -20,15 +20,19 @@ import android.annotation.NonNull; import android.app.Activity; import android.app.NotificationManager; import android.content.Intent; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.util.SparseArray; +import android.view.Gravity; import android.view.View; import androidx.leanback.widget.VerticalGridView; import com.android.systemui.R; +import java.util.function.Consumer; + import javax.inject.Inject; /** @@ -42,6 +46,7 @@ public class TvNotificationPanelActivity extends Activity implements private VerticalGridView mNotificationListView; private View mNotificationPlaceholder; private boolean mPanelAlreadyOpen = false; + private final Consumer<Boolean> mBlurConsumer = this::enableBlur; @Inject public TvNotificationPanelActivity(TvNotificationHandler tvNotificationHandler) { @@ -103,6 +108,33 @@ public class TvNotificationPanelActivity extends Activity implements } @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + getWindow().setGravity(Gravity.END); + getWindowManager().addCrossWindowBlurEnabledListener(mBlurConsumer); + } + + private void enableBlur(boolean enabled) { + if (enabled) { + int blurRadius = getResources().getDimensionPixelSize( + R.dimen.tv_notification_blur_radius); + getWindow().setBackgroundDrawable( + new ColorDrawable(getColor(R.color.tv_notification_blur_background_color))); + getWindow().setBackgroundBlurRadius(blurRadius); + } else { + getWindow().setBackgroundDrawable( + new ColorDrawable(getColor(R.color.tv_notification_default_background_color))); + getWindow().setBackgroundBlurRadius(0); + } + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getWindowManager().removeCrossWindowBlurEnabledListener(mBlurConsumer); + } + + @Override public void onDestroy() { super.onDestroy(); mTvNotificationHandler.setTvNotificationListener(null); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 0a3e83326e01..bbb2f1a5259a 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -94,6 +94,7 @@ public class ThemeOverlayApplier implements Dumpable { */ static final List<String> THEME_CATEGORIES = Lists.newArrayList( OVERLAY_CATEGORY_SYSTEM_PALETTE, + OVERLAY_CATEGORY_NEUTRAL_PALETTE, OVERLAY_CATEGORY_ICON_LAUNCHER, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_FONT, @@ -107,6 +108,7 @@ public class ThemeOverlayApplier implements Dumpable { @VisibleForTesting static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet( OVERLAY_CATEGORY_SYSTEM_PALETTE, + OVERLAY_CATEGORY_NEUTRAL_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, @@ -129,8 +131,9 @@ public class ThemeOverlayApplier implements Dumpable { mLauncherPackage = launcherPackage; mThemePickerPackage = themePickerPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( - OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR, - OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID)); + OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, + OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, + OVERLAY_CATEGORY_ICON_ANDROID)); mTargetPackageToCategories.put(SYSUI_PACKAGE, Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI)); mTargetPackageToCategories.put(SETTINGS_PACKAGE, @@ -158,10 +161,10 @@ public class ThemeOverlayApplier implements Dumpable { void applyCurrentUserOverlays( Map<String, OverlayIdentifier> categoryToPackage, FabricatedOverlay[] pendingCreation, - Set<UserHandle> userHandles) { + int currentUser, + Set<UserHandle> managedProfiles) { // Disable all overlays that have not been specified in the user setting. final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); - overlayCategoriesToDisable.removeAll(categoryToPackage.keySet()); final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream() .map(category -> mCategoryToTargetPackage.get(category)) .collect(Collectors.toSet()); @@ -172,6 +175,7 @@ public class ThemeOverlayApplier implements Dumpable { .filter(o -> mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) .filter(o -> overlayCategoriesToDisable.contains(o.category)) + .filter(o -> !categoryToPackage.containsValue(new OverlayIdentifier(o.packageName))) .filter(o -> o.isEnabled()) .map(o -> new Pair<>(o.category, o.packageName)) .collect(Collectors.toList()); @@ -183,17 +187,18 @@ public class ThemeOverlayApplier implements Dumpable { } } - // Toggle overlays in the order of THEME_CATEGORIES. + for (Pair<String, String> packageToDisable : overlaysToDisable) { + OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); + setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser, + managedProfiles, false); + } + for (String category : THEME_CATEGORIES) { if (categoryToPackage.containsKey(category)) { OverlayIdentifier overlayInfo = categoryToPackage.get(category); - setEnabled(transaction, overlayInfo, category, userHandles, true); + setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles, true); } } - for (Pair<String, String> packageToDisable : overlaysToDisable) { - OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); - setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false); - } mExecutor.execute(() -> { try { @@ -210,18 +215,30 @@ public class ThemeOverlayApplier implements Dumpable { } private void setEnabled(OverlayManagerTransaction.Builder transaction, - OverlayIdentifier identifier, String category, Set<UserHandle> handles, - boolean enabled) { + OverlayIdentifier identifier, String category, int currentUser, + Set<UserHandle> managedProfiles, boolean enabled) { if (DEBUG) { Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: " + category + ": " + enabled); } - for (UserHandle userHandle : handles) { - transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); - } - if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { + + transaction.setEnabled(identifier, enabled, currentUser); + if (currentUser != UserHandle.SYSTEM.getIdentifier() + && SYSTEM_USER_CATEGORIES.contains(category)) { transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier()); } + + // Do not apply Launcher or Theme picker overlays to managed users. Apps are not + // installed in there. + OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM); + if (overlayInfo == null || overlayInfo.targetPackageName.equals(mLauncherPackage) + || overlayInfo.targetPackageName.equals(mThemePickerPackage)) { + return; + } + + for (UserHandle userHandle : managedProfiles) { + transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 1f222d80f014..f19228783b88 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -43,7 +43,6 @@ import android.util.Log; import androidx.annotation.NonNull; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -55,14 +54,13 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; -import com.google.android.collect.Sets; - import org.json.JSONException; import org.json.JSONObject; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -86,13 +84,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { protected static final int PRIMARY = 0; protected static final int SECONDARY = 1; - protected static final int NEUTRAL = 1; - - // If lock screen wallpaper colors should also be considered when selecting the theme. - // Doing this has performance impact, given that overlays would need to be swapped when - // the device unlocks. - @VisibleForTesting - static final boolean USE_LOCK_SCREEN_WALLPAPER = false; + protected static final int NEUTRAL = 2; private final ThemeOverlayApplier mThemeManager; private final UserManager mUserManager; @@ -104,7 +96,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private final WallpaperManager mWallpaperManager; private final KeyguardStateController mKeyguardStateController; private final boolean mIsMonetEnabled; - private WallpaperColors mLockColors; private WallpaperColors mSystemColors; // If fabricated overlays were already created for the current theme. private boolean mNeedsOverlayCreation; @@ -118,6 +109,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private FabricatedOverlay mSecondaryOverlay; // Neutral system colors overlay private FabricatedOverlay mNeutralOverlay; + // If wallpaper color event will be accepted and change the UI colors. + private boolean mAcceptColorEvents = true; @Inject public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, @@ -147,13 +140,20 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + filter.addAction(Intent.ACTION_WALLPAPER_CHANGED); mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); - updateThemeOverlays(); + if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction()) + || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) { + if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); + reevaluateSystemTheme(true /* forceReload */); + } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) { + mAcceptColorEvents = true; + Log.i(TAG, "Allowing color events again"); + } } - }, filter, mBgExecutor, UserHandle.ALL); + }, filter, mMainExecutor, UserHandle.ALL); mSecureSettings.registerContentObserverForUser( Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), false, @@ -163,7 +163,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { int userId) { if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId); if (ActivityManager.getCurrentUser() == userId) { - updateThemeOverlays(); + reevaluateSystemTheme(true /* forceReload */); } } }, @@ -171,53 +171,34 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { // Upon boot, make sure we have the most up to date colors mBgExecutor.execute(() -> { - WallpaperColors lockColors = mWallpaperManager.getWallpaperColors( - WallpaperManager.FLAG_LOCK); WallpaperColors systemColor = mWallpaperManager.getWallpaperColors( WallpaperManager.FLAG_SYSTEM); mMainExecutor.execute(() -> { - if (USE_LOCK_SCREEN_WALLPAPER) { - mLockColors = lockColors; - } mSystemColors = systemColor; - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }); }); - if (USE_LOCK_SCREEN_WALLPAPER) { - mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - if (mLockColors == null) { - return; - } - // It's possible that the user has a lock screen wallpaper. On this case we'll - // end up with different colors after unlocking. - reevaluateSystemTheme(); - } - }); - } mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> { - if (USE_LOCK_SCREEN_WALLPAPER && (which & WallpaperManager.FLAG_LOCK) != 0) { - mLockColors = wallpaperColors; - if (DEBUG) { - Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); - } + if (!mAcceptColorEvents) { + Log.i(TAG, "Wallpaper color event rejected: " + wallpaperColors); + return; } + if (wallpaperColors != null && mAcceptColorEvents) { + mAcceptColorEvents = false; + } + if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { mSystemColors = wallpaperColors; if (DEBUG) { Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); } } - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }, null, UserHandle.USER_ALL); } - private void reevaluateSystemTheme() { - WallpaperColors currentColors = - mKeyguardStateController.isShowing() && mLockColors != null - ? mLockColors : mSystemColors; - + private void reevaluateSystemTheme(boolean forceReload) { + final WallpaperColors currentColors = mSystemColors; final int mainColor; final int accentCandidate; if (currentColors == null) { @@ -228,7 +209,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { accentCandidate = getAccentColor(currentColors); } - if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate) { + if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate + && !forceReload) { return; } @@ -309,6 +291,16 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName()); } + } else if (!mIsMonetEnabled && systemPalette != null) { + try { + // It's possible that we flipped the flag off and still have a @ColorInt in the + // setting. We need to sanitize the input, otherwise the overlay transaction will + // fail. + Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Same for accent color. @@ -322,6 +314,13 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName()); } + } else if (!mIsMonetEnabled && accentPalette != null) { + try { + Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Compatibility with legacy themes, where full packages were defined, instead of just @@ -337,10 +336,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier()); } - Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); + Set<UserHandle> managedProfiles = new HashSet<>(); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { if (userInfo.isManagedProfile()) { - userHandles.add(userInfo.getUserHandle()); + managedProfiles.add(userInfo.getUserHandle()); } } if (DEBUG) { @@ -352,16 +351,15 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mNeedsOverlayCreation = false; mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay - }, userHandles); + }, currentUser, managedProfiles); } else { - mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles); + mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser, + managedProfiles); } } @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER); - pw.println("mLockColors=" + mLockColors); pw.println("mSystemColors=" + mSystemColors); pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor)); @@ -370,5 +368,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { pw.println("mNeutralOverlay=" + mNeutralOverlay); pw.println("mIsMonetEnabled=" + mIsMonetEnabled); pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation); + pw.println("mAcceptColorEvents=" + mAcceptColorEvents); } } diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java index 365cd2a5d20b..fab1655b1262 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java @@ -187,10 +187,7 @@ public class SystemUIToast implements ToastPlugin.Toast { mPluginToast.onOrientationChange(orientation); } - mDefaultY = mContext.getResources().getDimensionPixelSize( - mToastStyleEnabled - ? com.android.systemui.R.dimen.toast_y_offset - : R.dimen.toast_y_offset); + mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); mDefaultGravity = mContext.getResources().getInteger(R.integer.config_toastDefaultGravity); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java index 78341edefbb2..5b66216f41be 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java @@ -43,11 +43,13 @@ public class TunerActivity extends Activity implements private static final String TAG_TUNER = "tuner"; private final DemoModeController mDemoModeController; + private final TunerService mTunerService; @Inject - TunerActivity(DemoModeController demoModeController) { + TunerActivity(DemoModeController demoModeController, TunerService tunerService) { super(); mDemoModeController = demoModeController; + mTunerService = tunerService; } protected void onCreate(Bundle savedInstanceState) { @@ -67,7 +69,7 @@ public class TunerActivity extends Activity implements "com.android.settings.action.DEMO_MODE"); final PreferenceFragment fragment = showDemoMode ? new DemoModeFragment(mDemoModeController) - : new TunerFragment(); + : new TunerFragment(mTunerService); getFragmentManager().beginTransaction().replace(R.id.content_frame, fragment, TAG_TUNER).commit(); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java index 4c724aeea9ae..989462a9fd34 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java @@ -15,7 +15,7 @@ */ package com.android.systemui.tuner; -import android.app.ActivityManager; +import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -23,7 +23,6 @@ import android.content.DialogInterface; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Build; import android.os.Bundle; -import android.os.UserHandle; import android.provider.Settings; import android.view.Menu; import android.view.MenuInflater; @@ -56,6 +55,15 @@ public class TunerFragment extends PreferenceFragment { private static final int MENU_REMOVE = Menu.FIRST + 1; + private final TunerService mTunerService; + + // We are the only ones who ever call this constructor, so don't worry about the warning + @SuppressLint("ValidFragment") + public TunerFragment(TunerService tunerService) { + super(); + mTunerService = tunerService; + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -124,13 +132,9 @@ public class TunerFragment extends PreferenceFragment { getActivity().finish(); return true; case MENU_REMOVE: - UserHandle user = new UserHandle(ActivityManager.getCurrentUser()); - TunerService.showResetRequest(getContext(), user, new Runnable() { - @Override - public void run() { - if (getActivity() != null) { - getActivity().finish(); - } + mTunerService.showResetRequest(() -> { + if (getActivity() != null) { + getActivity().finish(); } }); return true; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index b67574d1c4de..5d09e064604a 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -15,19 +15,10 @@ package com.android.systemui.tuner; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.UserHandle; -import android.provider.Settings; import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialog; public abstract class TunerService { @@ -47,6 +38,16 @@ public abstract class TunerService { public abstract void addTunable(Tunable tunable, String... keys); public abstract void removeTunable(Tunable tunable); + /** + * Sets the state of the {@link TunerActivity} component for the current user + */ + public abstract void setTunerEnabled(boolean enabled); + + /** + * Returns true if the tuner is enabled for the current user. + */ + public abstract boolean isTunerEnabled(); + public interface Tunable { void onTuningChanged(String key, String newValue); } @@ -55,38 +56,6 @@ public abstract class TunerService { mContext = context; } - private static Context userContext(Context context, UserHandle user) { - try { - return context.createPackageContextAsUser(context.getPackageName(), 0, user); - } catch (NameNotFoundException e) { - return context; - } - } - - /** Enables or disables the tuner for the supplied user. */ - public void setTunerEnabled(UserHandle user, boolean enabled) { - setTunerEnabled(mContext, user, enabled); - } - - public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) { - userContext(context, user).getPackageManager().setComponentEnabledSetting( - new ComponentName(context, TunerActivity.class), - enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - - /** Returns true if the tuner is enabled for the supplied user. */ - public boolean isTunerEnabled(UserHandle user) { - return isTunerEnabled(mContext, user); - } - - public static final boolean isTunerEnabled(Context context, UserHandle user) { - return userContext(context, user).getPackageManager().getComponentEnabledSetting( - new ComponentName(context, TunerActivity.class)) - == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; - } - public static class ClearReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -97,35 +66,7 @@ public abstract class TunerService { } /** */ - public void showResetRequest(UserHandle user, final Runnable onDisabled) { - showResetRequest(mContext, user, onDisabled); - } - - public static final void showResetRequest(final Context context, UserHandle user, - final Runnable onDisabled) { - SystemUIDialog dialog = new SystemUIDialog(context); - dialog.setShowForAllUsers(true); - dialog.setMessage(R.string.remove_from_settings_prompt); - dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel), - (OnClickListener) null); - dialog.setButton(DialogInterface.BUTTON_POSITIVE, - context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Tell the tuner (in main SysUI process) to clear all its settings. - context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR)); - // Disable access to tuner. - TunerService.setTunerEnabled(context, user, false); - // Make them sit through the warning dialog again. - Settings.Secure.putInt(context.getContentResolver(), - TunerFragment.SETTING_SEEN_TUNER_WARNING, 0); - if (onDisabled != null) { - onDisabled.run(); - } - } - }); - dialog.show(); - } + public abstract void showResetRequest(Runnable onDisabled); public static boolean parseIntegerSwitch(String value, boolean defaultValue) { try { diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 027c282ba352..7244ffed61b9 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -15,8 +15,12 @@ */ package com.android.systemui.tuner; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.net.Uri; @@ -32,13 +36,14 @@ import android.util.ArraySet; import com.android.internal.util.ArrayUtils; import com.android.systemui.DejankUtils; -import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.leak.LeakDetector; import java.util.HashSet; @@ -83,6 +88,7 @@ public class TunerServiceImpl extends TunerService { private int mCurrentUser; private UserTracker.Callback mCurrentUserTracker; private UserTracker mUserTracker; + private final ComponentName mTunerComponent; /** */ @@ -92,7 +98,6 @@ public class TunerServiceImpl extends TunerService { @Main Handler mainHandler, LeakDetector leakDetector, DemoModeController demoModeController, - BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) { super(context); mContext = context; @@ -100,6 +105,7 @@ public class TunerServiceImpl extends TunerService { mLeakDetector = leakDetector; mDemoModeController = demoModeController; mUserTracker = userTracker; + mTunerComponent = new ComponentName(mContext, TunerActivity.class); for (UserInfo user : UserManager.get(mContext).getUsers()) { mCurrentUser = user.getUserHandle().getIdentifier(); @@ -142,7 +148,7 @@ public class TunerServiceImpl extends TunerService { } } if (oldVersion < 2) { - setTunerEnabled(mContext, mUserTracker.getUserHandle(), false); + setTunerEnabled(false); } // 3 Removed because of a revert. if (oldVersion < 4) { @@ -269,6 +275,46 @@ public class TunerServiceImpl extends TunerService { } } + + @Override + public void setTunerEnabled(boolean enabled) { + mUserTracker.getUserContext().getPackageManager().setComponentEnabledSetting( + mTunerComponent, + enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP + ); + } + + @Override + public boolean isTunerEnabled() { + return mUserTracker.getUserContext().getPackageManager().getComponentEnabledSetting( + mTunerComponent) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + } + + @Override + public void showResetRequest(Runnable onDisabled) { + SystemUIDialog dialog = new SystemUIDialog(mContext); + dialog.setShowForAllUsers(true); + dialog.setMessage(R.string.remove_from_settings_prompt); + dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), + (DialogInterface.OnClickListener) null); + dialog.setButton(DialogInterface.BUTTON_POSITIVE, + mContext.getString(R.string.qs_customize_remove), (d, which) -> { + // Tell the tuner (in main SysUI process) to clear all its settings. + mContext.sendBroadcast(new Intent(TunerService.ACTION_CLEAR)); + // Disable access to tuner. + setTunerEnabled(false); + // Make them sit through the warning dialog again. + Secure.putInt(mContext.getContentResolver(), + TunerFragment.SETTING_SEEN_TUNER_WARNING, 0); + if (onDisabled != null) { + onDisabled.run(); + } + }); + dialog.show(); + } + private class Observer extends ContentObserver { public Observer() { super(new Handler(Looper.getMainLooper())); diff --git a/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java b/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java index 79a197d9d409..a22793b05070 100644 --- a/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java @@ -16,15 +16,18 @@ package com.android.systemui.util; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.graphics.drawable.DrawableWrapper; +import android.graphics.drawable.InsetDrawable; import android.util.AttributeSet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.systemui.R; import org.xmlpull.v1.XmlPullParser; @@ -45,13 +48,18 @@ import java.io.IOException; * @attr ref R.styleable#AlphaTintDrawableWrapper_tint * @attr ref R.styleable#AlphaTintDrawableWrapper_alpha */ -public class AlphaTintDrawableWrapper extends DrawableWrapper { +public class AlphaTintDrawableWrapper extends InsetDrawable { private ColorStateList mTint; private int[] mThemeAttrs; /** No-arg constructor used by drawable inflation. */ public AlphaTintDrawableWrapper() { - super(null); + super(null, 0); + } + + AlphaTintDrawableWrapper(Drawable drawable, int[] themeAttrs) { + super(drawable, 0); + mThemeAttrs = themeAttrs; } @Override @@ -74,7 +82,7 @@ public class AlphaTintDrawableWrapper extends DrawableWrapper { public void applyTheme(Theme t) { super.applyTheme(t); - if (mThemeAttrs != null) { + if (mThemeAttrs != null && t != null) { final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.AlphaTintDrawableWrapper); updateStateFromTypedArray(a); @@ -92,9 +100,6 @@ public class AlphaTintDrawableWrapper extends DrawableWrapper { } private void updateStateFromTypedArray(@NonNull TypedArray a) { - if (a.hasValue(R.styleable.AlphaTintDrawableWrapper_android_drawable)) { - setDrawable(a.getDrawable(R.styleable.AlphaTintDrawableWrapper_android_drawable)); - } if (a.hasValue(R.styleable.AlphaTintDrawableWrapper_android_tint)) { mTint = a.getColorStateList(R.styleable.AlphaTintDrawableWrapper_android_tint); } @@ -109,4 +114,57 @@ public class AlphaTintDrawableWrapper extends DrawableWrapper { getDrawable().mutate().setTintList(mTint); } } + + @Nullable + @Override + public ConstantState getConstantState() { + return new AlphaTintState(super.getConstantState(), mThemeAttrs, getAlpha(), mTint); + } + + static class AlphaTintState extends Drawable.ConstantState { + + private ConstantState mWrappedState; + private int[] mThemeAttrs; + private int mAlpha; + private ColorStateList mColorStateList; + + AlphaTintState( + ConstantState wrappedState, + int[] themeAttrs, + int alpha, + ColorStateList colorStateList + ) { + mWrappedState = wrappedState; + mThemeAttrs = themeAttrs; + mAlpha = alpha; + mColorStateList = colorStateList; + } + + @NonNull + @Override + public Drawable newDrawable() { + return newDrawable(null, null); + } + + @NonNull + @Override + public Drawable newDrawable(Resources res, Theme theme) { + DrawableWrapper wrapper = (DrawableWrapper) mWrappedState.newDrawable(res, theme); + AlphaTintDrawableWrapper alphaTintDrawableWrapper = + new AlphaTintDrawableWrapper(wrapper.getDrawable(), mThemeAttrs); + alphaTintDrawableWrapper.setTintList(mColorStateList); + alphaTintDrawableWrapper.setAlpha(mAlpha); + return alphaTintDrawableWrapper; + } + + @Override + public boolean canApplyTheme() { + return true; + } + + @Override + public int getChangingConfigurations() { + return mWrappedState.getChangingConfigurations(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt index 1af2c9f46373..6aadd1020bce 100644 --- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt @@ -17,15 +17,12 @@ package com.android.systemui.util import android.content.res.Resources -import android.content.res.TypedArray import android.graphics.Canvas import android.graphics.Path import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.DrawableWrapper -import android.util.AttributeSet -import com.android.systemui.R -import org.xmlpull.v1.XmlPullParser +import android.graphics.drawable.InsetDrawable /** * [DrawableWrapper] to use in the progress of a slider. @@ -38,9 +35,9 @@ import org.xmlpull.v1.XmlPullParser * is meant to be smaller than the rounded corner. The background should have rounded corners that * are half of the height. */ -class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawable) { - - constructor() : this(null) +class RoundedCornerProgressDrawable @JvmOverloads constructor( + drawable: Drawable? = null +) : InsetDrawable(drawable, 0) { companion object { private const val MAX_LEVEL = 10000 // Taken from Drawable @@ -52,35 +49,11 @@ class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawa setClipPath(Rect()) } - override fun inflate( - r: Resources, - parser: XmlPullParser, - attrs: AttributeSet, - theme: Resources.Theme? - ) { - val a = obtainAttributes(r, theme, attrs, R.styleable.RoundedCornerProgressDrawable) - - // Inflation will advance the XmlPullParser and AttributeSet. - super.inflate(r, parser, attrs, theme) - - updateStateFromTypedArray(a) - if (drawable == null) { - throw IllegalStateException("${this::class.java.simpleName} needs a drawable") - } - a.recycle() - } - override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean { onLevelChange(level) return super.onLayoutDirectionChanged(layoutDirection) } - private fun updateStateFromTypedArray(a: TypedArray) { - if (a.hasValue(R.styleable.RoundedCornerProgressDrawable_android_drawable)) { - setDrawable(a.getDrawable(R.styleable.RoundedCornerProgressDrawable_android_drawable)) - } - } - override fun onBoundsChange(bounds: Rect) { setClipPath(bounds) super.onBoundsChange(bounds) @@ -115,4 +88,24 @@ class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawa super.draw(canvas) canvas.restore() } + + override fun getConstantState(): ConstantState? { + // This should not be null as it was created with a state in the constructor. + return RoundedCornerState(super.getConstantState()!!) + } + + private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() { + override fun newDrawable(): Drawable { + return newDrawable(null, null) + } + + override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable { + val wrapper = wrappedState.newDrawable(res, theme) as DrawableWrapper + return RoundedCornerProgressDrawable(wrapper.drawable) + } + + override fun getChangingConfigurations(): Int { + return wrappedState.changingConfigurations + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 06806d0e6ab6..6a648bdf8cd4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -347,6 +347,7 @@ public class ProximitySensor implements ThresholdSensor { public void check(long timeoutMs, Consumer<Boolean> callback) { if (!mSensor.isLoaded()) { callback.accept(null); + return; } mCallbacks.add(callback); if (!mRegistered.getAndSet(true)) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index df54eabca8e7..5dc7006406ee 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -33,7 +33,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Dialog; @@ -51,6 +55,9 @@ import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.RotateDrawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Debug; @@ -81,6 +88,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; @@ -88,6 +97,7 @@ import android.widget.Toast; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -99,6 +109,8 @@ import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.AlphaTintDrawableWrapper; +import com.android.systemui.util.RoundedCornerProgressDrawable; import java.io.PrintWriter; import java.util.ArrayList; @@ -124,8 +136,14 @@ public class VolumeDialogImpl implements VolumeDialog, static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; + private static final int DRAWER_ANIMATION_DURATION_SHORT = 175; + private static final int DRAWER_ANIMATION_DURATION = 250; + private final int mDialogShowAnimationDurationMs; private final int mDialogHideAnimationDurationMs; + private final int mRingerDrawerItemSize; + private final boolean mShowVibrate; + private final int mRingerCount; private final boolean mShowLowMediaVolumeIcon; private final boolean mChangeVolumeRowTintWhenInactive; @@ -140,6 +158,30 @@ public class VolumeDialogImpl implements VolumeDialog, private ViewGroup mDialogView; private ViewGroup mDialogRowsView; private ViewGroup mRinger; + + private ViewGroup mSelectedRingerContainer; + private ImageView mSelectedRingerIcon; + + private ViewGroup mRingerDrawerContainer; + private ViewGroup mRingerDrawerMute; + private ViewGroup mRingerDrawerVibrate; + private ViewGroup mRingerDrawerNormal; + private ImageView mRingerDrawerMuteIcon; + private ImageView mRingerDrawerVibrateIcon; + private ImageView mRingerDrawerNormalIcon; + + /** + * View that draws the 'selected' background behind one of the three ringer choices in the + * drawer. + */ + private ViewGroup mRingerDrawerNewSelectionBg; + + private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f); + private ImageView mRingerDrawerIconAnimatingSelected; + private ImageView mRingerDrawerIconAnimatingDeselected; + + private boolean mIsRingerDrawerOpen = false; + private ImageButton mRingerIcon; private ViewGroup mODICaptionsView; private CaptionsToggleImageButton mODICaptionsIcon; @@ -191,6 +233,12 @@ public class VolumeDialogImpl implements VolumeDialog, mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs); mDialogHideAnimationDurationMs = mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs); + mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize( + R.dimen.volume_ringer_drawer_item_size); + mShowVibrate = mController.hasVibrator(); + + // Normal, mute, and possibly vibrate. + mRingerCount = mShowVibrate ? 3 : 2; } @Override @@ -314,6 +362,20 @@ public class VolumeDialogImpl implements VolumeDialog, mZenIcon = mRinger.findViewById(R.id.dnd_icon); } + mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon); + mSelectedRingerContainer = mDialog.findViewById( + R.id.volume_new_ringer_active_icon_container); + + mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute); + mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal); + mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate); + mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon); + mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon); + mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); + mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); + + setupRingerDrawer(); + mODICaptionsView = mDialog.findViewById(R.id.odi_captions); if (mODICaptionsView != null) { mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); @@ -475,38 +537,279 @@ public class VolumeDialogImpl implements VolumeDialog, row.anim = null; + final LayerDrawable seekbarDrawable = + (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar); + + final LayerDrawable seekbarBgDrawable = + (LayerDrawable) seekbarDrawable.findDrawableByLayerId(android.R.id.background); + + row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_solid); + + row.sliderBgIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_icon)).getDrawable(); + + final LayerDrawable seekbarProgressDrawable = (LayerDrawable) + ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId( + android.R.id.progress)).getDrawable(); + + row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_solid); + + row.sliderProgressIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_icon)).getDrawable(); + + row.slider.setProgressDrawable(seekbarDrawable); + row.slider.setThumb(null); + row.icon = row.view.findViewById(R.id.volume_row_icon); - row.icon.setImageResource(iconRes); - if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { - row.icon.setOnClickListener(v -> { - Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); - mController.setActiveStream(row.stream); - if (row.stream == AudioManager.STREAM_RING) { - final boolean hasVibrator = mController.hasVibrator(); - if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { - if (hasVibrator) { - mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + + row.setIcon(iconRes, mContext.getTheme()); + + if (row.icon != null) { + if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { + row.icon.setOnClickListener(v -> { + Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); + mController.setActiveStream(row.stream); + if (row.stream == AudioManager.STREAM_RING) { + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + } else { + final boolean wasZero = row.ss.level == 0; + mController.setStreamVolume(stream, + wasZero ? row.lastAudibleLevel : 0); + } } else { - final boolean wasZero = row.ss.level == 0; - mController.setStreamVolume(stream, - wasZero ? row.lastAudibleLevel : 0); + mController.setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + if (row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } } } else { - mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); - if (row.ss.level == 0) { - mController.setStreamVolume(stream, 1); - } + final boolean vmute = row.ss.level == row.ss.levelMin; + mController.setStreamVolume(stream, + vmute ? row.lastAudibleLevel : row.ss.levelMin); } - } else { - final boolean vmute = row.ss.level == row.ss.levelMin; - mController.setStreamVolume(stream, - vmute ? row.lastAudibleLevel : row.ss.levelMin); - } - row.userAttempt = 0; // reset the grace period, slider updates immediately - }); + row.userAttempt = 0; // reset the grace period, slider updates immediately + }); + } else { + row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + } + } + + private void setRingerMode(int newRingerMode) { + Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); + incrementManualToggleCount(); + updateRingerH(); + provideTouchFeedbackH(newRingerMode); + mController.setRingerMode(newRingerMode, false); + maybeShowToastH(newRingerMode); + } + + private void setupRingerDrawer() { + mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container); + + if (mRingerDrawerContainer == null) { + return; + } + + if (!mShowVibrate) { + mRingerDrawerVibrate.setVisibility(GONE); + } + + // In portrait, add padding to the bottom to account for the height of the open ringer + // drawer. + if (!isLandscape()) { + mDialogView.setPadding( + mDialogView.getPaddingLeft(), + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom() + (mRingerCount - 1) * mRingerDrawerItemSize); + } else { + mDialogView.setPadding( + mDialogView.getPaddingLeft() + (mRingerCount - 1) * mRingerDrawerItemSize, + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom()); + } + + ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options)) + .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); + + mSelectedRingerContainer.setOnClickListener(view -> { + if (mIsRingerDrawerOpen) { + hideRingerDrawer(); + } else { + showRingerDrawer(); + } + }); + + mRingerDrawerVibrate.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE)); + mRingerDrawerMute.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_SILENT)); + mRingerDrawerNormal.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_NORMAL)); + + final int unselectedColor = Utils.getColorAccentDefaultColor(mContext); + final int selectedColor = Utils.getColorAttrDefaultColor( + mContext, android.R.attr.colorBackgroundFloating); + + // Add an update listener that animates the deselected icon to the unselected color, and the + // selected icon to the selected color. + mRingerDrawerIconColorAnimator.addUpdateListener( + anim -> { + final float currentValue = (float) anim.getAnimatedValue(); + final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, selectedColor, unselectedColor); + final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, unselectedColor, selectedColor); + + mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor); + mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor); + }); + mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRingerDrawerIconAnimatingDeselected.clearColorFilter(); + mRingerDrawerIconAnimatingSelected.clearColorFilter(); + } + }); + mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT); + } + + private ImageView getDrawerIconViewForMode(int mode) { + if (mode == RINGER_MODE_VIBRATE) { + return mRingerDrawerVibrateIcon; + } else if (mode == RINGER_MODE_SILENT) { + return mRingerDrawerMuteIcon; + } else { + return mRingerDrawerNormalIcon; + } + } + + /** + * Translation to apply form the origin (either top or left) to overlap the selection background + * with the given mode in the drawer. + */ + private float getTranslationInDrawerForRingerMode(int mode) { + return mode == RINGER_MODE_VIBRATE + ? -mRingerDrawerItemSize * 2 + : mode == RINGER_MODE_SILENT + ? -mRingerDrawerItemSize + : 0; + } + + /** Animates in the ringer drawer. */ + private void showRingerDrawer() { + // Show all ringer icons except the currently selected one, since we're going to animate the + // ringer button to that position. + mRingerDrawerVibrateIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE); + mRingerDrawerMuteIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE); + mRingerDrawerNormalIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE); + + // Hide the selection background - we use this to show a selection when one is + // tapped, so it should be invisible until that happens. However, position it below + // the currently selected ringer so that it's ready to animate. + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.setTranslationY( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } else { + mRingerDrawerNewSelectionBg.setTranslationX( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } + + // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer + // icon. + if (!isLandscape()) { + mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1)); + } else { + mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1)); + } + mRingerDrawerContainer.setAlpha(0f); + mRingerDrawerContainer.setVisibility(VISIBLE); + + // Animate the drawer up and visible. + mRingerDrawerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + // Vibrate is way farther up, so give the selected ringer icon a head start if + // vibrate is selected. + .setDuration(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION_SHORT + : DRAWER_ANIMATION_DURATION) + .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT + : 0) + .alpha(1f) + .translationX(0f) + .translationY(0f) + .start(); + + // Animate the selected ringer view up to that ringer's position in the drawer. + mSelectedRingerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setDuration(DRAWER_ANIMATION_DURATION) + .withEndAction(() -> + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE)); + + if (!isLandscape()) { + mSelectedRingerContainer.animate() + .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); + } else { + mSelectedRingerContainer.animate() + .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); + } + + mIsRingerDrawerOpen = true; + } + + /** Animates away the ringer drawer. */ + private void hideRingerDrawer() { + + // If the ringer drawer isn't present, don't try to hide it. + if (mRingerDrawerContainer == null) { + return; + } + + // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we + // don't want to be able to see it while it animates away. + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); + + mRingerDrawerContainer.animate() + .alpha(0f) + .setDuration(DRAWER_ANIMATION_DURATION) + .setStartDelay(0) + .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE)); + + if (!isLandscape()) { + mRingerDrawerContainer.animate() + .translationY(mRingerDrawerItemSize * 2) + .start(); } else { - row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mRingerDrawerContainer.animate() + .translationX(mRingerDrawerItemSize * 2) + .start(); } + + mSelectedRingerContainer.animate() + .translationX(0f) + .translationY(0f) + .start(); + + mIsRingerDrawerOpen = false; } public void initSettingsH() { @@ -555,12 +858,8 @@ public class VolumeDialogImpl implements VolumeDialog, mController.setStreamVolume(AudioManager.STREAM_RING, 1); } } - Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); - incrementManualToggleCount(); - updateRingerH(); - provideTouchFeedbackH(newRingerMode); - mController.setRingerMode(newRingerMode, false); - maybeShowToastH(newRingerMode); + + setRingerMode(newRingerMode); }); } updateRingerH(); @@ -809,6 +1108,8 @@ public class VolumeDialogImpl implements VolumeDialog, mDialog.dismiss(); tryToRemoveCaptionsTooltip(); mIsAnimatingDismiss = false; + + hideRingerDrawer(); }, 50)); if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f); animator.start(); @@ -889,12 +1190,14 @@ public class VolumeDialogImpl implements VolumeDialog, switch (mState.ringerModeInternal) { case AudioManager.RINGER_MODE_VIBRATE: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE, mContext.getString(R.string.volume_ringer_hint_mute)); mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); break; case AudioManager.RINGER_MODE_SILENT: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); mRingerIcon.setTag(Events.ICON_STATE_MUTE); addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT, mContext.getString(R.string.volume_ringer_hint_unmute)); @@ -904,11 +1207,13 @@ public class VolumeDialogImpl implements VolumeDialog, boolean muted = (mAutomute && ss.level == 0) || ss.muted; if (!isZenMuted && muted) { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_unmute)); mRingerIcon.setTag(Events.ICON_STATE_MUTE); } else { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer); if (mController.hasVibrator()) { addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_vibrate)); @@ -1075,8 +1380,6 @@ public class VolumeDialogImpl implements VolumeDialog, // update icon final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; - row.icon.setEnabled(iconEnabled); - row.icon.setAlpha(iconEnabled ? 1 : 0.5f); final int iconRes; if (isRingVibrate) { iconRes = R.drawable.ic_volume_ringer_vibrate; @@ -1092,7 +1395,7 @@ public class VolumeDialogImpl implements VolumeDialog, ? R.drawable.ic_volume_media_low : row.iconRes; } - row.icon.setImageResource(iconRes); + row.setIcon(iconRes, mContext.getTheme()); row.iconState = iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) @@ -1101,18 +1404,35 @@ public class VolumeDialogImpl implements VolumeDialog, || iconRes == R.drawable.ic_volume_media_low) ? Events.ICON_STATE_UNMUTE : Events.ICON_STATE_UNKNOWN; - if (iconEnabled) { - if (isRingStream) { - if (isRingVibrate) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); + + if (row.icon != null) { + if (iconEnabled) { + if (isRingStream) { + if (isRingVibrate) { + row.icon.setContentDescription(mContext.getString( + R.string.volume_stream_content_description_unmute, + getStreamLabelH(ss))); + } else { + if (mController.hasVibrator()) { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_vibrate_a11y + : R.string.volume_stream_content_description_vibrate, + getStreamLabelH(ss))); + } else { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_mute_a11y + : R.string.volume_stream_content_description_mute, + getStreamLabelH(ss))); + } + } + } else if (isA11yStream) { + row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (mController.hasVibrator()) { + if (ss.muted || mAutomute && ss.level == 0) { row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_vibrate_a11y - : R.string.volume_stream_content_description_vibrate, + R.string.volume_stream_content_description_unmute, getStreamLabelH(ss))); } else { row.icon.setContentDescription(mContext.getString( @@ -1122,23 +1442,9 @@ public class VolumeDialogImpl implements VolumeDialog, getStreamLabelH(ss))); } } - } else if (isA11yStream) { - row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (ss.muted || mAutomute && ss.level == 0) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); - } else { - row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_mute_a11y - : R.string.volume_stream_content_description_mute, - getStreamLabelH(ss))); - } + row.icon.setContentDescription(getStreamLabelH(ss)); } - } else { - row.icon.setContentDescription(getStreamLabelH(ss)); } // ensure tracking is disabled if zenMuted @@ -1167,22 +1473,29 @@ public class VolumeDialogImpl implements VolumeDialog, if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) { return; } - final ColorStateList tint = useActiveColoring + final ColorStateList colorTint = useActiveColoring ? Utils.getColorAccent(mContext) : Utils.getColorAttr(mContext, android.R.attr.colorForeground); final int alpha = useActiveColoring - ? Color.alpha(tint.getDefaultColor()) + ? Color.alpha(colorTint.getDefaultColor()) : getAlphaAttr(android.R.attr.secondaryContentAlpha); - if (tint == row.cachedTint) return; - row.slider.setProgressTintList(tint); - row.slider.setThumbTintList(tint); - row.slider.setProgressBackgroundTintList(tint); - row.slider.setAlpha(((float) alpha) / 255); - row.icon.setImageTintList(tint); - row.icon.setImageAlpha(alpha); - row.cachedTint = tint; + + final ColorStateList bgTint = Utils.getColorAttr( + mContext, android.R.attr.colorBackgroundFloating); + + row.sliderProgressSolid.setTintList(colorTint); + row.sliderBgIcon.setTintList(colorTint); + + row.sliderBgSolid.setTintList(bgTint); + row.sliderProgressIcon.setTintList(bgTint); + + if (row.icon != null) { + row.icon.setImageTintList(colorTint); + row.icon.setImageAlpha(alpha); + } + if (row.number != null) { - row.number.setTextColor(tint); + row.number.setTextColor(colorTint); row.number.setAlpha(alpha); } } @@ -1538,6 +1851,10 @@ public class VolumeDialogImpl implements VolumeDialog, private View view; private TextView header; private ImageButton icon; + private Drawable sliderBgSolid; + private AlphaTintDrawableWrapper sliderBgIcon; + private Drawable sliderProgressSolid; + private AlphaTintDrawableWrapper sliderProgressIcon; private SeekBar slider; private TextView number; private int stream; @@ -1555,5 +1872,69 @@ public class VolumeDialogImpl implements VolumeDialog, private int animTargetProgress; private int lastAudibleLevel = 1; private FrameLayout dndIcon; + + void setIcon(int iconRes, Resources.Theme theme) { + if (icon != null) { + icon.setImageResource(iconRes); + } + + sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + } + } + + /** + * Click listener added to each ringer option in the drawer. This will initiate the animation to + * select and then close the ringer drawer, and actually change the ringer mode. + */ + private class RingerDrawerItemClickListener implements View.OnClickListener { + private final int mClickedRingerMode; + + RingerDrawerItemClickListener(int clickedRingerMode) { + mClickedRingerMode = clickedRingerMode; + } + + @Override + public void onClick(View view) { + setRingerMode(mClickedRingerMode); + + mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode); + mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode( + mState.ringerModeInternal); + + // Begin switching the selected icon and deselected icon colors since the background is + // going to animate behind the new selection. + mRingerDrawerIconColorAnimator.start(); + + mSelectedRingerContainer.setVisibility(View.INVISIBLE); + mRingerDrawerNewSelectionBg.setAlpha(1f); + mRingerDrawerNewSelectionBg.animate() + .setInterpolator(Interpolators.ACCELERATE_DECELERATE) + .setDuration(DRAWER_ANIMATION_DURATION_SHORT) + .withEndAction(() -> { + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mSelectedRingerContainer.setTranslationY( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } else { + mSelectedRingerContainer.setTranslationX( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } + + mSelectedRingerContainer.setVisibility(VISIBLE); + hideRingerDrawer(); + }); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.animate() + .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } else { + mRingerDrawerNewSelectionBg.animate() + .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java index ff2881953342..82dad68f238c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java @@ -19,16 +19,15 @@ package com.android.systemui.wmshell; import android.content.Context; import android.os.Handler; -import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; @@ -140,6 +139,7 @@ public abstract class TvPipModule { @Provides static PipTaskOrganizer providePipTaskOrganizer(Context context, TvPipMenuController tvPipMenuController, + SyncTransactionQueue syncTransactionQueue, PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, @@ -149,7 +149,8 @@ public abstract class TvPipModule { DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { - return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm, + return new PipTaskOrganizer(context, + syncTransactionQueue, pipBoundsState, pipBoundsAlgorithm, tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper, pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index fba0b0079012..a12326961b08 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -17,6 +17,7 @@ package com.android.systemui.wmshell; import static android.os.Process.THREAD_PRIORITY_DISPLAY; +import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; import android.animation.AnimationHandler; import android.app.ActivityTaskManager; @@ -31,6 +32,7 @@ import android.view.WindowManager; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.R; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -60,6 +62,7 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ChoreographerSfVsync; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; @@ -76,6 +79,8 @@ import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.startingsurface.StartingSurface; +import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.RemoteTransitions; import com.android.wm.shell.transition.Transitions; @@ -97,7 +102,12 @@ import dagger.Provides; @Module public abstract class WMShellBaseModule { - private static final boolean ENABLE_SHELL_MAIN_THREAD = false; + /** + * Returns whether to enable a separate shell thread for the shell features. + */ + private static boolean enableShellMainThread(Context context) { + return context.getResources().getBoolean(R.bool.config_enableShellMainThread); + } // // Shell Concurrency - Components used for managing threading in the Shell and SysUI @@ -120,8 +130,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides @ShellMainThread - public static Handler provideShellMainHandler(@Main Handler sysuiMainHandler) { - if (ENABLE_SHELL_MAIN_THREAD) { + public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) { + if (enableShellMainThread(context)) { HandlerThread mainThread = new HandlerThread("wmshell.main"); mainThread.start(); return mainThread.getThreadHandler(); @@ -135,9 +145,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides @ShellMainThread - public static ShellExecutor provideShellMainExecutor(@ShellMainThread Handler mainHandler, - @Main ShellExecutor sysuiMainExecutor) { - if (ENABLE_SHELL_MAIN_THREAD) { + public static ShellExecutor provideShellMainExecutor(Context context, + @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) { + if (enableShellMainThread(context)) { return new HandlerExecutor(mainHandler); } return sysuiMainExecutor; @@ -157,6 +167,19 @@ public abstract class WMShellBaseModule { } /** + * Provides a Shell splashscreen-thread Executor + */ + @WMSingleton + @Provides + @ShellSplashscreenThread + public static ShellExecutor provideSplashScreenExecutor() { + HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen", + THREAD_PRIORITY_TOP_APP_BOOST); + shellSplashscreenThread.start(); + return new HandlerExecutor(shellSplashscreenThread.getThreadHandler()); + } + + /** * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on * the Shell main-thread with the SF vsync. @@ -320,12 +343,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<OneHandedController> provideOneHandedController(Context context, - DisplayController displayController, TaskStackListenerImpl taskStackListener, - UiEventLogger uiEventLogger, + WindowManager windowManager, DisplayController displayController, + TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { - return Optional.ofNullable(OneHandedController.create(context, displayController, - taskStackListener, uiEventLogger, mainExecutor, mainHandler)); + return Optional.ofNullable(OneHandedController.create(context, windowManager, + displayController, taskStackListener, uiEventLogger, mainExecutor, mainHandler)); } // @@ -367,6 +390,9 @@ public abstract class WMShellBaseModule { return new PipUiEventLogger(uiEventLogger, packageManager); } + @BindsOptionalOf + abstract PipTouchHandler optionalPipTouchHandler(); + // // Shell transitions // @@ -380,9 +406,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool, - @ShellMainThread ShellExecutor mainExecutor, + Context context, @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor) { - return new Transitions(organizer, pool, mainExecutor, animExecutor); + return new Transitions(organizer, pool, context, mainExecutor, animExecutor); } // @@ -409,10 +435,11 @@ public abstract class WMShellBaseModule { ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + DisplayImeController displayImeController) { if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context, - rootTaskDisplayAreaOrganizer, mainExecutor)); + rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController)); } else { return Optional.empty(); } @@ -441,6 +468,22 @@ public abstract class WMShellBaseModule { @BindsOptionalOf abstract AppPairsController optionalAppPairs(); + // Starting window + + @WMSingleton + @Provides + static Optional<StartingSurface> provideStartingSurface( + StartingWindowController startingWindowController) { + return Optional.of(startingWindowController.asStartingSurface()); + } + + @WMSingleton + @Provides + static StartingWindowController provideStartingWindowController(Context context, + @ShellSplashscreenThread ShellExecutor executor) { + return new StartingWindowController(context, executor); + } + // // Task view factory // @@ -472,6 +515,8 @@ public abstract class WMShellBaseModule { Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, + Optional<StartingSurface> startingSurface, + Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, @ShellMainThread ShellExecutor mainExecutor) { @@ -481,6 +526,8 @@ public abstract class WMShellBaseModule { legacySplitScreenOptional, splitScreenOptional, appPairsOptional, + startingSurface, + pipTouchHandlerOptional, fullscreenTaskListener, transitions, mainExecutor); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 997b488a627f..d5183f85ad13 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -104,9 +104,10 @@ public class WMShellModule { @Provides static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, DisplayController displayController, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + DisplayImeController displayImeController) { return new AppPairsController(shellTaskOrganizer, syncQueue, displayController, - mainExecutor); + mainExecutor, displayImeController); } // @@ -171,6 +172,7 @@ public class WMShellModule { @WMSingleton @Provides static PipTaskOrganizer providePipTaskOrganizer(Context context, + SyncTransactionQueue syncTransactionQueue, PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm, PhonePipMenuController menuPhoneController, @@ -181,7 +183,8 @@ public class WMShellModule { DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { - return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm, + return new PipTaskOrganizer(context, + syncTransactionQueue, pipBoundsState, pipBoundsAlgorithm, menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper, pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); |