diff options
Diffstat (limited to 'packages/SystemUI/src/com')
129 files changed, 5002 insertions, 1086 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 92f89d6b90fd..a383cab94c36 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -20,12 +20,16 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.graphics.Color; import android.icu.text.NumberFormat; +import androidx.annotation.VisibleForTesting; + import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BatteryController; @@ -67,20 +71,20 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie BroadcastDispatcher broadcastDispatcher, BatteryController batteryController, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController bypassController) { + KeyguardBypassController bypassController, + @Main Resources resources + ) { super(view); mStatusBarStateController = statusBarStateController; - mIsDozing = mStatusBarStateController.isDozing(); - mDozeAmount = mStatusBarStateController.getDozeAmount(); mBroadcastDispatcher = broadcastDispatcher; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mBypassController = bypassController; mBatteryController = batteryController; mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER); - mBurmeseLineSpacing = getContext().getResources().getFloat( + mBurmeseLineSpacing = resources.getFloat( R.dimen.keyguard_clock_line_spacing_scale_burmese); - mDefaultLineSpacing = getContext().getResources().getFloat( + mDefaultLineSpacing = resources.getFloat( R.dimen.keyguard_clock_line_spacing_scale); } @@ -106,7 +110,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie } }; - private final StatusBarStateController.StateListener mStatusBarStatePersistentListener = + private final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override public void onDozeAmountChanged(float linear, float eased) { @@ -144,11 +148,11 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); mDozeAmount = mStatusBarStateController.getDozeAmount(); + mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0; mBatteryController.addCallback(mBatteryCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); - mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener); - mStatusBarStateController.addCallback(mStatusBarStatePersistentListener); + mStatusBarStateController.addCallback(mStatusBarStateListener); refreshTime(); initColors(); @@ -160,9 +164,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mBatteryController.removeCallback(mBatteryCallback); - if (!mView.isAttachedToWindow()) { - mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener); - } + mStatusBarStateController.removeCallback(mStatusBarStateListener); } /** Animate the clock appearance */ @@ -191,6 +193,14 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie mView.refreshFormat(); } + /** + * Return locallly stored dozing state. + */ + @VisibleForTesting + public boolean isDozing() { + return mIsDozing; + } + private void updateLocale() { Locale currLocale = Locale.getDefault(); if (!Objects.equals(currLocale, mLocale)) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index f4a3fb236ffa..7a01b4e8c971 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.app.WallpaperManager; +import android.content.res.Resources; import android.text.TextUtils; import android.view.View; import android.widget.FrameLayout; @@ -30,6 +31,7 @@ import com.android.keyguard.clock.ClockManager; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -65,18 +67,22 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final BroadcastDispatcher mBroadcastDispatcher; private final BatteryController mBatteryController; private final LockscreenSmartspaceController mSmartspaceController; + private final Resources mResources; /** * Clock for both small and large sizes */ private AnimatableClockController mClockViewController; - private FrameLayout mClockFrame; + private FrameLayout mClockFrame; // top aligned clock private AnimatableClockController mLargeClockViewController; - private FrameLayout mLargeClockFrame; + private FrameLayout mLargeClockFrame; // centered clock private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final KeyguardBypassController mBypassController; + private int mLargeClockTopMargin = 0; + private int mKeyguardClockTopMargin = 0; + /** * Listener for changes to the color palette. * @@ -113,7 +119,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS KeyguardBypassController bypassController, LockscreenSmartspaceController smartspaceController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - SmartspaceTransitionController smartspaceTransitionController) { + SmartspaceTransitionController smartspaceTransitionController, + @Main Resources resources) { super(keyguardClockSwitch); mStatusBarStateController = statusBarStateController; mColorExtractor = colorExtractor; @@ -125,6 +132,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardUpdateMonitor = keyguardUpdateMonitor; mBypassController = bypassController; mSmartspaceController = smartspaceController; + mResources = resources; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mSmartspaceTransitionController = smartspaceTransitionController; @@ -154,7 +162,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController); + mBypassController, + mResources); mClockViewController.init(); mLargeClockViewController = @@ -164,7 +173,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController); + mBypassController, + mResources); mLargeClockViewController.init(); } @@ -175,6 +185,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } mColorExtractor.addOnColorsChangedListener(mColorsListener); mView.updateColors(getGradientColors()); + mKeyguardClockTopMargin = + mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); if (mOnlyClock) { View ksa = mView.findViewById(R.id.keyguard_status_area); @@ -249,6 +261,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS */ public void onDensityOrFontScaleChanged() { mView.onDensityOrFontScaleChanged(); + mKeyguardClockTopMargin = + mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); updateClockLayout(); } @@ -257,9 +271,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (mSmartspaceController.isEnabled()) { RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); - lp.topMargin = getContext().getResources().getDimensionPixelSize( + mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize( R.dimen.keyguard_large_clock_top_margin); + lp.topMargin = mLargeClockTopMargin; mLargeClockFrame.setLayoutParams(lp); + } else { + mLargeClockTopMargin = 0; } } @@ -369,6 +386,28 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } + /** + * Get y-bottom position of the currently visible clock on the keyguard. + * We can't directly getBottom() because clock changes positions in AOD for burn-in + */ + int getClockBottom(int statusBarHeaderHeight) { + if (mLargeClockFrame.getVisibility() == View.VISIBLE) { + View clock = mLargeClockFrame.findViewById( + com.android.systemui.R.id.animatable_clock_view_large); + int frameHeight = mLargeClockFrame.getHeight(); + int clockHeight = clock.getHeight(); + return frameHeight / 2 + clockHeight / 2; + } else { + return mClockFrame.findViewById( + com.android.systemui.R.id.animatable_clock_view).getHeight() + + statusBarHeaderHeight + mKeyguardClockTopMargin; + } + } + + boolean isClockTopAligned() { + return mLargeClockFrame.getVisibility() != View.VISIBLE; + } + private void updateAodIcons() { NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 72e502816534..6b3e9c27c25f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -185,6 +185,20 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** + * Get y-bottom position of the currently visible clock. + */ + public int getClockBottom(int statusBarHeaderHeight) { + return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight); + } + + /** + * @return true if the currently displayed clock is top aligned (as opposed to center aligned) + */ + public boolean isClockTopAligned() { + return mKeyguardClockSwitchController.isClockTopAligned(); + } + + /** * Set whether the view accessibility importance mode. */ public void setStatusAccessibilityImportance(int mode) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 28a54d56b071..e115c342c4fe 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -132,8 +132,7 @@ public class KeyguardVisibilityHelper { .alpha(1f) .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) .start(); - } else if (mUnlockedScreenOffAnimationController - .isScreenOffLightRevealAnimationPlaying()) { + } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) { mKeyguardViewVisibilityAnimating = true; // Ask the screen off animation controller to animate the keyguard visibility for us diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 5c34bebdaa4e..ef4353b93179 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -27,6 +27,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; @@ -47,6 +48,7 @@ public class LockIconView extends FrameLayout implements Dumpable { private ImageView mBgView; private int mLockIconColor; + private boolean mUseBackground = false; public LockIconView(Context context, AttributeSet attrs) { super(context, attrs); @@ -60,8 +62,8 @@ public class LockIconView extends FrameLayout implements Dumpable { mBgView = findViewById(R.id.lock_icon_bg); } - void updateColorAndBackgroundVisibility(boolean useBackground) { - if (useBackground && mLockIcon.getDrawable() != null) { + void updateColorAndBackgroundVisibility() { + if (mUseBackground && mLockIcon.getDrawable() != null) { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary); mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); @@ -77,6 +79,9 @@ public class LockIconView extends FrameLayout implements Dumpable { void setImageDrawable(Drawable drawable) { mLockIcon.setImageDrawable(drawable); + + if (!mUseBackground) return; + if (drawable == null) { mBgView.setVisibility(View.INVISIBLE); } else { @@ -84,6 +89,18 @@ public class LockIconView extends FrameLayout implements Dumpable { } } + /** + * Whether or not to render the lock icon background. Mainly used for UDPFS. + */ + public void setUseBackground(boolean useBackground) { + mUseBackground = useBackground; + updateColorAndBackgroundVisibility(); + } + + /** + * Set the location of the lock icon. + */ + @VisibleForTesting public void setCenterLocation(@NonNull PointF center, int radius) { mLockIconCenter = center; mRadius = radius; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index a41997ce3107..52ebf2fa09d0 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -49,6 +49,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; @@ -72,7 +73,8 @@ import javax.inject.Inject; /** * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen. * - * This view will only be shown if the user has UDFPS or FaceAuth enrolled + * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock + * icon will show a set distance from the bottom of the device. */ @StatusBarComponent.StatusBarScope public class LockIconViewController extends ViewController<LockIconView> implements Dumpable { @@ -106,6 +108,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private CharSequence mUnlockedLabel; @NonNull private CharSequence mLockedLabel; @Nullable private final Vibrator mVibrator; + @Nullable private final AuthRippleController mAuthRippleController; private boolean mIsDozing; private boolean mIsBouncerShowing; @@ -149,7 +152,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull AccessibilityManager accessibilityManager, @NonNull ConfigurationController configurationController, @NonNull @Main DelayableExecutor executor, - @Nullable Vibrator vibrator + @Nullable Vibrator vibrator, + @Nullable AuthRippleController authRippleController ) { super(view); mStatusBarStateController = statusBarStateController; @@ -162,6 +166,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mConfigurationController = configurationController; mExecutor = executor; mVibrator = vibrator; + mAuthRippleController = authRippleController; final Context context = view.getContext(); mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp); @@ -340,7 +345,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } private void updateColors() { - mView.updateColorAndBackgroundVisibility(mUdfpsSupported); + mView.updateColorAndBackgroundVisibility(); } private void updateConfiguration() { @@ -420,6 +425,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme boolean wasUdfpsEnrolled = mUdfpsEnrolled; mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mView.setUseBackground(mUdfpsSupported); + mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { updateVisibility(); @@ -621,6 +628,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme // pre-emptively set to true to hide view mIsBouncerShowing = true; + if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { + mAuthRippleController.showRipple(FINGERPRINT); + } updateVisibility(); if (mOnGestureDetectedRunnable != null) { mOnGestureDetectedRunnable.run(); diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java index 3a0357d2a284..4331f52eb1a2 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java @@ -16,7 +16,8 @@ package com.android.keyguard.dagger; -import com.android.systemui.statusbar.phone.UserAvatarView; +import android.widget.FrameLayout; + import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import dagger.BindsInstance; @@ -31,8 +32,7 @@ public interface KeyguardQsUserSwitchComponent { /** Simple factory for {@link KeyguardUserSwitcherComponent}. */ @Subcomponent.Factory interface Factory { - KeyguardQsUserSwitchComponent build( - @BindsInstance UserAvatarView userAvatarView); + KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer); } /** Builds a {@link KeyguardQsUserSwitchController}. */ diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 76f30a80114a..00b33a416df4 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -65,6 +65,7 @@ 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.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.screenrecord.RecordingController; @@ -364,6 +365,7 @@ public class Dependency { @Inject Lazy<UiEventLogger> mUiEventLogger; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; + @Inject Lazy<InternetDialogFactory> mInternetDialogFactory; @Inject public Dependency() { @@ -578,6 +580,7 @@ public class Dependency { mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); mProviders.put(EdgeBackGestureHandler.Factory.class, mEdgeBackGestureHandlerFactoryLazy::get); + mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index a68f79604b25..8379ccf3154a 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -20,6 +20,8 @@ import android.app.WallpaperColors; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.RectF; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; @@ -29,7 +31,6 @@ import android.util.ArraySet; import android.util.Log; import android.util.MathUtils; import android.util.Size; -import android.view.DisplayInfo; import android.view.SurfaceHolder; import android.view.WindowManager; @@ -90,7 +91,7 @@ public class ImageWallpaper extends WallpaperService { mMiniBitmap = null; } - class GLEngine extends Engine { + class GLEngine extends Engine implements DisplayListener { // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin) // set min to 64 px (CTS covers this), please refer to ag/4867989 for detail. @VisibleForTesting @@ -102,15 +103,15 @@ public class ImageWallpaper extends WallpaperService { private EglHelper mEglHelper; private final Runnable mFinishRenderingTask = this::finishRendering; private boolean mNeedRedraw; - private int mWidth = 1; - private int mHeight = 1; + + private boolean mDisplaySizeValid = false; + private int mDisplayWidth = 1; + private int mDisplayHeight = 1; + private int mImgWidth = 1; private int mImgHeight = 1; - private float mPageWidth = 1.f; - private float mPageOffset = 1.f; - GLEngine() { - } + GLEngine() { } @VisibleForTesting GLEngine(Handler handler) { @@ -119,18 +120,29 @@ public class ImageWallpaper extends WallpaperService { @Override public void onCreate(SurfaceHolder surfaceHolder) { + Trace.beginSection("ImageWallpaper.Engine#onCreate"); mEglHelper = getEglHelperInstance(); // Deferred init renderer because we need to get wallpaper by display context. mRenderer = getRendererInstance(); setFixedSizeAllowed(true); updateSurfaceSize(); - Rect window = getDisplayContext() - .getSystemService(WindowManager.class) - .getCurrentWindowMetrics() - .getBounds(); - mHeight = window.height(); - mWidth = window.width(); mRenderer.setOnBitmapChanged(this::updateMiniBitmap); + getDisplayContext().getSystemService(DisplayManager.class) + .registerDisplayListener(this, mWorker.getThreadHandler()); + Trace.endSection(); + } + + @Override + public void onDisplayAdded(int displayId) { } + + @Override + public void onDisplayRemoved(int displayId) { } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == getDisplayContext().getDisplayId()) { + mDisplaySizeValid = false; + } } EglHelper getEglHelperInstance() { @@ -154,26 +166,10 @@ public class ImageWallpaper extends WallpaperService { if (pages == mPages) return; mPages = pages; if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return; - updateShift(); mWorker.getThreadHandler().post(() -> computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap)); } - private void updateShift() { - if (mImgHeight == 0) { - mPageOffset = 0; - mPageWidth = 1; - return; - } - // calculate shift - DisplayInfo displayInfo = new DisplayInfo(); - getDisplayContext().getDisplay().getDisplayInfo(displayInfo); - int screenWidth = displayInfo.getNaturalWidth(); - float imgWidth = Math.min(mImgWidth > 0 ? screenWidth / (float) mImgWidth : 1.f, 1.f); - mPageWidth = imgWidth; - mPageOffset = (1 - imgWidth) / (float) (mPages - 1); - } - private void updateMiniBitmap(Bitmap b) { if (b == null) return; int size = Math.min(b.getWidth(), b.getHeight()); @@ -203,13 +199,22 @@ public class ImageWallpaper extends WallpaperService { } @Override + public boolean shouldWaitForEngineShown() { + return true; + } + + @Override public void onDestroy() { + getDisplayContext().getSystemService(DisplayManager.class) + .unregisterDisplayListener(this); mMiniBitmap = null; mWorker.getThreadHandler().post(() -> { + Trace.beginSection("ImageWallpaper.Engine#onDestroy"); mRenderer.finish(); mRenderer = null; mEglHelper.finish(); mEglHelper = null; + Trace.endSection(); }); } @@ -268,6 +273,16 @@ public class ImageWallpaper extends WallpaperService { * (1-Wr)]. */ private RectF pageToImgRect(RectF area) { + if (!mDisplaySizeValid) { + Rect window = getDisplayContext() + .getSystemService(WindowManager.class) + .getCurrentWindowMetrics() + .getBounds(); + mDisplayWidth = window.width(); + mDisplayHeight = window.height(); + mDisplaySizeValid = true; + } + // Width of a page for the caller of this API. float virtualPageWidth = 1f / (float) mPages; float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth; @@ -275,12 +290,24 @@ public class ImageWallpaper extends WallpaperService { int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth); RectF imgArea = new RectF(); + + if (mImgWidth == 0 || mImgHeight == 0 || mDisplayWidth <= 0 || mDisplayHeight <= 0) { + return imgArea; + } + imgArea.bottom = area.bottom; imgArea.top = area.top; + + float imageScale = Math.min(((float) mImgHeight) / mDisplayHeight, 1); + float mappedScreenWidth = mDisplayWidth * imageScale; + float pageWidth = Math.min(1.0f, + mImgWidth > 0 ? mappedScreenWidth / (float) mImgWidth : 1.f); + float pageOffset = (1 - pageWidth) / (float) (mPages - 1); + imgArea.left = MathUtils.constrain( - leftPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1); + leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1); imgArea.right = MathUtils.constrain( - rightPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1); + rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1); if (imgArea.left > imgArea.right) { // take full page imgArea.left = 0; @@ -293,7 +320,6 @@ public class ImageWallpaper extends WallpaperService { private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas, Bitmap b) { List<WallpaperColors> colors = new ArrayList<>(areas.size()); - updateShift(); for (int i = 0; i < areas.size(); i++) { RectF area = pageToImgRect(areas.get(i)); if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) { @@ -322,8 +348,10 @@ public class ImageWallpaper extends WallpaperService { public void onSurfaceCreated(SurfaceHolder holder) { if (mWorker == null) return; mWorker.getThreadHandler().post(() -> { + Trace.beginSection("ImageWallpaper#onSurfaceCreated"); mEglHelper.init(holder, needSupportWideColorGamut()); mRenderer.onSurfaceCreated(); + Trace.endSection(); }); } @@ -340,9 +368,11 @@ public class ImageWallpaper extends WallpaperService { } private void drawFrame() { + Trace.beginSection("ImageWallpaper#drawFrame"); preRender(); requestRender(); postRender(); + Trace.endSection(); } public void preRender() { @@ -409,6 +439,7 @@ public class ImageWallpaper extends WallpaperService { // This method should only be invoked from worker thread. Trace.beginSection("ImageWallpaper#postRender"); scheduleFinishRendering(); + reportEngineShown(false /* waitForEngineShown */); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index affad7a57d86..8c63f7bec23c 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -438,6 +438,12 @@ public class SwipeHelper implements Gefingerpoken { private boolean mCancelled; @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mCallback.onBeginDrag(animView); + } + + @Override public void onAnimationCancel(Animator animation) { mCancelled = true; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java index d8e80fe99331..0d7551ff66e9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java @@ -30,7 +30,7 @@ import java.util.Optional; /** * A span that turns the text wrapped by annotation tag into the clickable link text. */ -class AnnotationLinkSpan extends ClickableSpan { +public class AnnotationLinkSpan extends ClickableSpan { private final Optional<View.OnClickListener> mClickListener; private AnnotationLinkSpan(View.OnClickListener listener) { @@ -50,7 +50,7 @@ class AnnotationLinkSpan extends ClickableSpan { * @param linkInfos used to attach the click action into the corresponding span * @return the text attached with the span */ - static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) { + public static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) { final SpannableString msg = new SpannableString(text); final Annotation[] spans = msg.getSpans(/* queryStart= */ 0, msg.length(), Annotation.class); @@ -78,12 +78,12 @@ class AnnotationLinkSpan extends ClickableSpan { /** * Data class to store the annotation and the click action. */ - static class LinkInfo { - static final String DEFAULT_ANNOTATION = "link"; + public static class LinkInfo { + public static final String DEFAULT_ANNOTATION = "link"; private final Optional<String> mAnnotation; private final Optional<View.OnClickListener> mListener; - LinkInfo(@NonNull String annotation, View.OnClickListener listener) { + public LinkInfo(@NonNull String annotation, View.OnClickListener listener) { mAnnotation = Optional.of(annotation); mListener = Optional.ofNullable(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java index 81a13a236685..408201558a9b 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java @@ -57,7 +57,9 @@ public class AssistOrbController { public void run() { mView.removeCallbacks(this); mView.show(false /* show */, true /* animate */, () -> { - mWindowManager.removeView(mView); + if (mView.isAttachedToWindow()) { + mWindowManager.removeView(mView); + } }); } }; diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt index c9e67715decb..5616a00592f2 100644 --- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt @@ -40,10 +40,10 @@ import com.android.systemui.people.widget.PeopleBackupHelper * After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0, * indicating that restoring is finished for a given user. */ -class BackupHelper : BackupAgentHelper() { +open class BackupHelper : BackupAgentHelper() { companion object { - private const val TAG = "BackupHelper" + const val TAG = "BackupHelper" internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite" private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 3f61d3c6af9a..fd37b3509a4e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -126,6 +126,7 @@ public class AuthContainerView extends LinearLayout boolean mCredentialAllowed; boolean mSkipIntro; long mOperationId; + long mRequestId; @BiometricMultiSensorMode int mMultiSensorConfig; } @@ -172,6 +173,12 @@ public class AuthContainerView extends LinearLayout return this; } + /** Unique id for this request. */ + public Builder setRequestId(long requestId) { + mConfig.mRequestId = requestId; + return this; + } + /** The multi-sensor mode. */ public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) { mConfig.mMultiSensorConfig = multiSensorConfig; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index a4123c769d1e..0790af94f287 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -42,6 +42,7 @@ import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.FingerprintStateListener; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Bundle; @@ -49,6 +50,7 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import android.util.SparseBooleanArray; import android.view.MotionEvent; import android.view.WindowManager; @@ -76,6 +78,9 @@ import kotlin.Unit; /** * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the * appropriate biometric UI (e.g. BiometricDialogView). + * + * Also coordinates biometric-related things, such as UDFPS, with + * {@link com.android.keyguard.KeyguardUpdateMonitor} */ @SysUISingleton public class AuthController extends SystemUI implements CommandQueue.Callbacks, @@ -115,6 +120,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps; @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; + @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; + private class BiometricTaskStackListener extends TaskStackListener { @Override public void onTaskStackChanged() { @@ -122,6 +129,21 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } } + private final FingerprintStateListener mFingerprintStateListener = + new FingerprintStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + Log.d(TAG, "onEnrollmentsChanged, userId: " + userId + + ", sensorId: " + sensorId + + ", hasEnrollments: " + hasEnrollments); + for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) { + if (prop.sensorId == sensorId) { + mUdfpsEnrolledForUser.put(userId, hasEnrollments); + } + } + } + }; + @NonNull private final IFingerprintAuthenticatorsRegisteredCallback mFingerprintAuthenticatorsRegisteredCallback = @@ -436,6 +458,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mUdfpsControllerFactory = udfpsControllerFactory; mSidefpsControllerFactory = sidefpsControllerFactory; mWindowManager = windowManager; + mUdfpsEnrolledForUser = new SparseBooleanArray(); mOrientationListener = new BiometricOrientationEventListener(context, () -> { onOrientationChanged(); @@ -474,6 +497,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, if (mFingerprintManager != null) { mFingerprintManager.addAuthenticatorsRegisteredCallback( mFingerprintAuthenticatorsRegisteredCallback); + mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener); } mTaskStackListener = new BiometricTaskStackListener(); @@ -501,7 +525,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Override public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, - int userId, String opPackageName, long operationId, + int userId, long operationId, String opPackageName, long requestId, @BiometricMultiSensorMode int multiSensorConfig) { @Authenticators.Types final int authenticators = promptInfo.getAuthenticators(); @@ -515,6 +539,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, + ", credentialAllowed: " + credentialAllowed + ", requireConfirmation: " + requireConfirmation + ", operationId: " + operationId + + ", requestId: " + requestId + ", multiSensorConfig: " + multiSensorConfig); } SomeArgs args = SomeArgs.obtain(); @@ -526,6 +551,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, args.argi1 = userId; args.arg6 = opPackageName; args.arg7 = operationId; + args.arg8 = requestId; args.argi2 = multiSensorConfig; boolean skipAnimation = false; @@ -629,6 +655,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, if (mCurrentDialog == null) { // Could be possible if the caller canceled authentication after credential success // but before the client was notified. + if (DEBUG) Log.d(TAG, "dialog already gone"); return; } @@ -670,7 +697,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, return false; } - return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps); + return mUdfpsEnrolledForUser.get(userId); } private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { @@ -683,6 +710,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, final int userId = args.argi1; final String opPackageName = (String) args.arg6; final long operationId = (long) args.arg7; + final long requestId = (long) args.arg8; final @BiometricMultiSensorMode int multiSensorConfig = args.argi2; // Create a new dialog but do not replace the current one yet. @@ -695,6 +723,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, opPackageName, skipAnimation, operationId, + requestId, multiSensorConfig); if (newDialog == null) { @@ -772,7 +801,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName, - boolean skipIntro, long operationId, + boolean skipIntro, long operationId, long requestId, @BiometricMultiSensorMode int multiSensorConfig) { return new AuthContainerView.Builder(mContext) .setCallback(this) @@ -782,10 +811,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, .setOpPackageName(opPackageName) .setSkipIntro(skipIntro) .setOperationId(operationId) + .setRequestId(requestId) .setMultiSensorConfig(multiSensorConfig) .build(sensorIds, credentialAllowed, mFpProps, mFaceProps); } + /** + * AuthController callback used to receive signal for when biometric authenticators are + * registered. + */ public interface Callback { /** * Called when authenticators are registered. If authenticators are already diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 54f932184331..9d4a67dfddba 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -39,11 +39,9 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; import android.media.AudioAttributes; import android.os.Handler; -import android.os.Looper; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.SystemClock; import android.os.Trace; import android.os.VibrationEffect; import android.os.Vibrator; @@ -64,7 +62,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -72,10 +69,12 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import com.android.systemui.util.time.SystemClock; import java.util.HashSet; import java.util.Optional; @@ -93,7 +92,7 @@ import kotlin.Unit; * controls/manages all UDFPS sensors. In other words, a single controller is registered with * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or - * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have + * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have * {@code sensorId} parameters. */ @SuppressWarnings("deprecation") @@ -117,9 +116,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @NonNull private final DumpManager mDumpManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @NonNull private final KeyguardViewMediator mKeyguardViewMediator; @Nullable private final Vibrator mVibrator; - @NonNull private final Handler mMainHandler; @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; @@ -127,6 +124,9 @@ public class UdfpsController implements DozeReceiver { @Nullable private final UdfpsHbmProvider mHbmProvider; @NonNull private final KeyguardBypassController mKeyguardBypassController; @NonNull private final ConfigurationController mConfigurationController; + @NonNull private final SystemClock mSystemClock; + @NonNull private final UnlockedScreenOffAnimationController + mUnlockedScreenOffAnimationController; @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener; // 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. @@ -165,7 +165,8 @@ public class UdfpsController implements DozeReceiver { public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + // vibration will bypass battery saver mode: + .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY) .build(); public static final VibrationEffect EFFECT_CLICK = @@ -243,7 +244,7 @@ public class UdfpsController implements DozeReceiver { final UdfpsEnrollHelper enrollHelper; if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) { - enrollHelper = new UdfpsEnrollHelper(mContext, reason); + enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason); } else { enrollHelper = null; } @@ -454,19 +455,19 @@ public class UdfpsController implements DozeReceiver { final String touchInfo = String.format( "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", minor, major, v, exceedsVelocityThreshold); - final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime; + final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; if (!isIlluminationRequested && !mGoodCaptureReceived && !exceedsVelocityThreshold) { onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); - mTouchLogTime = SystemClock.elapsedRealtime(); - mPowerManager.userActivity(SystemClock.uptimeMillis(), + mTouchLogTime = mSystemClock.elapsedRealtime(); + mPowerManager.userActivity(mSystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); handled = true; } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { Log.v(TAG, "onTouch | finger move: " + touchInfo); - mTouchLogTime = SystemClock.elapsedRealtime(); + mTouchLogTime = mSystemClock.elapsedRealtime(); } } else { Log.v(TAG, "onTouch | finger outside"); @@ -517,7 +518,6 @@ public class UdfpsController implements DozeReceiver { @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull KeyguardViewMediator keyguardViewMediator, @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, @@ -530,11 +530,12 @@ public class UdfpsController implements DozeReceiver { @NonNull KeyguardBypassController keyguardBypassController, @NonNull DisplayManager displayManager, @Main Handler mainHandler, - @NonNull ConfigurationController configurationController) { + @NonNull ConfigurationController configurationController, + @NonNull SystemClock systemClock, + @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mContext = context; mExecution = execution; // TODO (b/185124905): inject main handler and vibrator once done prototyping - mMainHandler = new Handler(Looper.getMainLooper()); mVibrator = vibrator; mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the @@ -548,7 +549,6 @@ public class UdfpsController implements DozeReceiver { mKeyguardViewManager = statusBarKeyguardViewManager; mDumpManager = dumpManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mKeyguardViewMediator = keyguardViewMediator; mFalsingManager = falsingManager; mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; @@ -566,6 +566,8 @@ public class UdfpsController implements DozeReceiver { mainHandler); mKeyguardBypassController = keyguardBypassController; mConfigurationController = configurationController; + mSystemClock = systemClock; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -655,6 +657,20 @@ public class UdfpsController implements DozeReceiver { } } + private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) { + if (!(animation instanceof UdfpsKeyguardViewController)) { + // always rotate view if we're not on the keyguard + return true; + } + + // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded + if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) { + return false; + } + + return true; + } + private WindowManager.LayoutParams computeLayoutParams( @Nullable UdfpsAnimationViewController animation) { final int paddingX = animation != null ? animation.getPaddingX() : 0; @@ -678,9 +694,11 @@ public class UdfpsController implements DozeReceiver { // Transform dimensions if the device is in landscape mode switch (mContext.getDisplay().getRotation()) { case Surface.ROTATION_90: - if (animation instanceof UdfpsKeyguardViewController - && mKeyguardUpdateMonitor.isGoingToSleep()) { + if (!shouldRotate(animation)) { + Log.v(TAG, "skip rotating udfps location ROTATION_90"); break; + } else { + Log.v(TAG, "rotate udfps location ROTATION_90"); } mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingX; @@ -689,9 +707,11 @@ public class UdfpsController implements DozeReceiver { break; case Surface.ROTATION_270: - if (animation instanceof UdfpsKeyguardViewController - && mKeyguardUpdateMonitor.isGoingToSleep()) { + if (!shouldRotate(animation)) { + Log.v(TAG, "skip rotating udfps location ROTATION_270"); break; + } else { + Log.v(TAG, "rotate udfps location ROTATION_270"); } mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingX; @@ -708,15 +728,21 @@ public class UdfpsController implements DozeReceiver { return mCoreLayoutParams; } + private void onOrientationChanged() { // When the configuration changes it's almost always necessary to destroy and re-create // the overlay's window to pass it the new LayoutParams. // Hiding the overlay will destroy its window. It's safe to hide the overlay regardless // of whether it is already hidden. + final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); hideUdfpsOverlay(); + // If the overlay needs to be shown, this will re-create and show the overlay with the // updated LayoutParams. Otherwise, the overlay will remain hidden. updateOverlay(); + if (wasShowingAltAuth) { + mKeyguardViewManager.showGenericBouncer(true); + } } private void showUdfpsOverlay(@NonNull ServerRequest request) { @@ -782,12 +808,12 @@ public class UdfpsController implements DozeReceiver { mStatusBar, mKeyguardViewManager, mKeyguardUpdateMonitor, - mFgExecutor, mDumpManager, - mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, + mSystemClock, mKeyguardStateController, + mUnlockedScreenOffAnimationController, this ); case IUdfpsOverlayController.REASON_AUTH_BP: @@ -823,10 +849,14 @@ public class UdfpsController implements DozeReceiver { Log.v(TAG, "hideUdfpsOverlay | removing window"); // Reset the controller back to its starting state. onFingerUp(); + boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); mWindowManager.removeView(mView); mView.setOnTouchListener(null); mView.setOnHoverListener(null); mView.setAnimationViewController(null); + if (wasShowingAltAuth) { + mKeyguardViewManager.resetAlternateAuth(true); + } mAccessibilityManager.removeTouchExplorationStateChangeListener( mTouchExplorationStateChangeListener); mView = null; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 8ac6df7198b7..f720d54bc746 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.PointF; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Build; import android.os.UserHandle; @@ -44,16 +45,6 @@ public class UdfpsEnrollHelper { private static final String NEW_COORDS_OVERRIDE = "com.android.systemui.biometrics.UdfpsNewCoords"; - static final int ENROLL_STAGE_COUNT = 4; - - // TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling - private static final int[] STAGE_THRESHOLDS = new int[] { - 2, // center - 18, // guided - 22, // fingertip - 38, // edges - }; - interface Listener { void onEnrollmentProgress(int remaining, int totalSteps); void onEnrollmentHelp(int remaining, int totalSteps); @@ -61,6 +52,7 @@ public class UdfpsEnrollHelper { } @NonNull private final Context mContext; + @NonNull private final FingerprintManager mFingerprintManager; // IUdfpsOverlayController reason private final int mEnrollReason; private final boolean mAccessibilityEnabled; @@ -77,8 +69,11 @@ public class UdfpsEnrollHelper { @Nullable Listener mListener; - public UdfpsEnrollHelper(@NonNull Context context, int reason) { + public UdfpsEnrollHelper(@NonNull Context context, + @NonNull FingerprintManager fingerprintManager, int reason) { + mContext = context; + mFingerprintManager = fingerprintManager; mEnrollReason = reason; final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); @@ -127,12 +122,12 @@ public class UdfpsEnrollHelper { } } - static int getStageThreshold(int index) { - return STAGE_THRESHOLDS[index]; + int getStageCount() { + return mFingerprintManager.getEnrollStageCount(); } - static int getLastStageThreshold() { - return STAGE_THRESHOLDS[ENROLL_STAGE_COUNT - 1]; + int getStageThresholdSteps(int totalSteps, int stageIndex) { + return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex)); } boolean shouldShowProgressBar() { @@ -153,19 +148,6 @@ public class UdfpsEnrollHelper { } } - if (mTotalSteps == -1) { - mTotalSteps = remaining; - - // Allocate (or subtract) any extra steps for the first enroll stage. - final int extraSteps = mTotalSteps - getLastStageThreshold(); - if (extraSteps != 0) { - for (int stageIndex = 0; stageIndex < ENROLL_STAGE_COUNT; stageIndex++) { - STAGE_THRESHOLDS[stageIndex] = - Math.max(0, STAGE_THRESHOLDS[stageIndex] + extraSteps); - } - } - } - mRemainingSteps = remaining; if (mListener != null) { @@ -194,7 +176,7 @@ public class UdfpsEnrollHelper { if (mTotalSteps == -1 || mRemainingSteps == -1) { return true; } - return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[0]; + return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0); } boolean isGuidedEnrollmentStage() { @@ -202,7 +184,8 @@ public class UdfpsEnrollHelper { return false; } final int progressSteps = mTotalSteps - mRemainingSteps; - return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1]; + return progressSteps >= getStageThresholdSteps(mTotalSteps, 0) + && progressSteps < getStageThresholdSteps(mTotalSteps, 1); } boolean isTipEnrollmentStage() { @@ -210,14 +193,15 @@ public class UdfpsEnrollHelper { return false; } final int progressSteps = mTotalSteps - mRemainingSteps; - return progressSteps >= STAGE_THRESHOLDS[1] && progressSteps < STAGE_THRESHOLDS[2]; + return progressSteps >= getStageThresholdSteps(mTotalSteps, 1) + && progressSteps < getStageThresholdSteps(mTotalSteps, 2); } boolean isEdgeEnrollmentStage() { if (mTotalSteps == -1 || mRemainingSteps == -1) { return false; } - return mTotalSteps - mRemainingSteps >= STAGE_THRESHOLDS[2]; + return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2); } @NonNull diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index b56543f4851b..f772720c3cf1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -36,72 +36,109 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { private static final float SEGMENT_GAP_ANGLE = 12f; - @NonNull private final List<UdfpsEnrollProgressBarSegment> mSegments; + @NonNull private final Context mContext; + + @Nullable private UdfpsEnrollHelper mEnrollHelper; + @NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>(); + private int mTotalSteps = 1; + private int mProgressSteps = 0; + private boolean mIsShowingHelp = false; public UdfpsEnrollProgressBarDrawable(@NonNull Context context) { - mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT); - float startAngle = SEGMENT_GAP_ANGLE / 2f; - final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE; - final Runnable invalidateRunnable = this::invalidateSelf; - for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) { - mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle, - sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable)); - startAngle += sweepAngle + SEGMENT_GAP_ANGLE; - } + mContext = context; } - void setEnrollmentProgress(int remaining, int totalSteps) { - if (remaining == totalSteps) { - // Show some progress for the initial touch. - setEnrollmentProgress(1); - } else { - setEnrollmentProgress(totalSteps - remaining); + void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) { + mEnrollHelper = enrollHelper; + if (enrollHelper != null) { + final int stageCount = enrollHelper.getStageCount(); + mSegments = new ArrayList<>(stageCount); + float startAngle = SEGMENT_GAP_ANGLE / 2f; + final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE; + final Runnable invalidateRunnable = this::invalidateSelf; + for (int index = 0; index < stageCount; index++) { + mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle, + sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable)); + startAngle += sweepAngle + SEGMENT_GAP_ANGLE; + } + invalidateSelf(); } } - private void setEnrollmentProgress(int progressSteps) { - Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps); + void onEnrollmentProgress(int remaining, int totalSteps) { + mTotalSteps = totalSteps; + updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */); + } - int segmentIndex = 0; - int prevThreshold = 0; - while (segmentIndex < mSegments.size()) { - final UdfpsEnrollProgressBarSegment segment = mSegments.get(segmentIndex); - final int threshold = UdfpsEnrollHelper.getStageThreshold(segmentIndex); + void onEnrollmentHelp(int remaining, int totalSteps) { + updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */); + } + + void onLastStepAcquired() { + updateState(mTotalSteps, false /* isShowingHelp */); + } + + private static int getProgressSteps(int remaining, int totalSteps) { + // Show some progress for the initial touch. + return Math.max(1, totalSteps - remaining); + } + + private void updateState(int progressSteps, boolean isShowingHelp) { + updateProgress(progressSteps); + updateFillColor(isShowingHelp); + } + + private void updateProgress(int progressSteps) { + if (mProgressSteps == progressSteps) { + return; + } + mProgressSteps = progressSteps; - if (progressSteps >= threshold && !segment.isFilledOrFilling()) { - Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] complete"); + if (mEnrollHelper == null) { + Log.e(TAG, "updateState: UDFPS enroll helper was null"); + return; + } + + int index = 0; + int prevThreshold = 0; + while (index < mSegments.size()) { + final UdfpsEnrollProgressBarSegment segment = mSegments.get(index); + final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index); + if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) { segment.updateProgress(1f); break; - } else if (progressSteps >= prevThreshold && progressSteps < threshold) { + } else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) { final int relativeSteps = progressSteps - prevThreshold; - final int relativeThreshold = threshold - prevThreshold; + final int relativeThreshold = thresholdSteps - prevThreshold; final float segmentProgress = (float) relativeSteps / (float) relativeThreshold; - Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] progress = " - + segmentProgress); segment.updateProgress(segmentProgress); break; } - segmentIndex++; - prevThreshold = threshold; + index++; + prevThreshold = thresholdSteps; } - if (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) { - Log.d(TAG, "setEnrollmentProgress: startCompletionAnimation"); + if (progressSteps >= mTotalSteps) { for (final UdfpsEnrollProgressBarSegment segment : mSegments) { segment.startCompletionAnimation(); } } else { - Log.d(TAG, "setEnrollmentProgress: cancelCompletionAnimation"); for (final UdfpsEnrollProgressBarSegment segment : mSegments) { segment.cancelCompletionAnimation(); } } } - void onLastStepAcquired() { - Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired"); - setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold()); + private void updateFillColor(boolean isShowingHelp) { + if (mIsShowingHelp == isShowingHelp) { + return; + } + mIsShowingHelp = isShowingHelp; + + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.updateFillColor(isShowingHelp); + } } @Override @@ -123,12 +160,10 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { @Override public void setAlpha(int alpha) { - } @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { - } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java index 5f24380b6ce3..bd6ab4443630 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java @@ -39,6 +39,7 @@ import com.android.systemui.R; public class UdfpsEnrollProgressBarSegment { private static final String TAG = "UdfpsProgressBarSegment"; + private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L; private static final long PROGRESS_ANIMATION_DURATION_MS = 400L; private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L; private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L; @@ -53,16 +54,21 @@ public class UdfpsEnrollProgressBarSegment { private final float mSweepAngle; private final float mMaxOverSweepAngle; private final float mStrokeWidthPx; + @ColorInt private final int mProgressColor; + @ColorInt private final int mHelpColor; @NonNull private final Paint mBackgroundPaint; @NonNull private final Paint mProgressPaint; - private boolean mIsFilledOrFilling = false; - private float mProgress = 0f; + private float mAnimatedProgress = 0f; @Nullable private ValueAnimator mProgressAnimator; @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener; + private boolean mIsShowingHelp = false; + @Nullable private ValueAnimator mFillColorAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener; + private float mOverSweepAngle = 0f; @Nullable private ValueAnimator mOverSweepAnimator; @Nullable private ValueAnimator mOverSweepReverseAnimator; @@ -79,6 +85,8 @@ public class UdfpsEnrollProgressBarSegment { mSweepAngle = sweepAngle; mMaxOverSweepAngle = maxOverSweepAngle; mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP); + mProgressColor = context.getColor(R.color.udfps_enroll_progress); + mHelpColor = context.getColor(R.color.udfps_enroll_progress_help); mBackgroundPaint = new Paint(); mBackgroundPaint.setStrokeWidth(mStrokeWidthPx); @@ -100,13 +108,18 @@ public class UdfpsEnrollProgressBarSegment { // Progress should not be color extracted mProgressPaint = new Paint(); mProgressPaint.setStrokeWidth(mStrokeWidthPx); - mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress)); + mProgressPaint.setColor(mProgressColor); mProgressPaint.setAntiAlias(true); mProgressPaint.setStyle(Paint.Style.STROKE); mProgressPaint.setStrokeCap(Paint.Cap.ROUND); mProgressUpdateListener = animation -> { - mProgress = (float) animation.getAnimatedValue(); + mAnimatedProgress = (float) animation.getAnimatedValue(); + mInvalidateRunnable.run(); + }; + + mFillColorUpdateListener = animation -> { + mProgressPaint.setColor((int) animation.getAnimatedValue()); mInvalidateRunnable.run(); }; @@ -129,11 +142,9 @@ public class UdfpsEnrollProgressBarSegment { * Draws this segment to the given canvas. */ public void draw(@NonNull Canvas canvas) { - Log.d(TAG, "draw: mProgress = " + mProgress); - final float halfPaddingPx = mStrokeWidthPx / 2f; - if (mProgress < 1f) { + if (mAnimatedProgress < 1f) { // Draw the unfilled background color of the segment. canvas.drawArc( halfPaddingPx, @@ -146,7 +157,7 @@ public class UdfpsEnrollProgressBarSegment { mBackgroundPaint); } - if (mProgress > 0f) { + if (mAnimatedProgress > 0f) { // Draw the filled progress portion of the segment. canvas.drawArc( halfPaddingPx, @@ -154,17 +165,18 @@ public class UdfpsEnrollProgressBarSegment { mBounds.right - halfPaddingPx, mBounds.bottom - halfPaddingPx, mStartAngle, - mSweepAngle * mProgress + mOverSweepAngle, + mSweepAngle * mAnimatedProgress + mOverSweepAngle, false /* useCenter */, mProgressPaint); } } /** - * @return Whether this segment is filled or in the process of being filled. + * @return The fill progress of this segment, in the range [0, 1]. If fill progress is being + * animated, returns the value it is animating to. */ - public boolean isFilledOrFilling() { - return mIsFilledOrFilling; + public float getProgress() { + return mProgress; } /** @@ -177,27 +189,44 @@ public class UdfpsEnrollProgressBarSegment { } private void updateProgress(float progress, long animationDurationMs) { - Log.d(TAG, "updateProgress: progress = " + progress - + ", duration = " + animationDurationMs); - if (mProgress == progress) { - Log.d(TAG, "updateProgress skipped: progress == mProgress"); return; } - - mIsFilledOrFilling = progress >= 1f; + mProgress = progress; if (mProgressAnimator != null && mProgressAnimator.isRunning()) { mProgressAnimator.cancel(); } - mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); + mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress); mProgressAnimator.setDuration(animationDurationMs); mProgressAnimator.addUpdateListener(mProgressUpdateListener); mProgressAnimator.start(); } /** + * Updates the fill color of this segment, animating if necessary. + * + * @param isShowingHelp Whether fill color should indicate that a help message is being shown. + */ + public void updateFillColor(boolean isShowingHelp) { + if (mIsShowingHelp == isShowingHelp) { + return; + } + mIsShowingHelp = isShowingHelp; + + if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) { + mFillColorAnimator.cancel(); + } + + @ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor; + mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor); + mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS); + mFillColorAnimator.addUpdateListener(mFillColorUpdateListener); + mFillColorAnimator.start(); + } + + /** * Queues and runs the completion animation for this segment. */ public void startCompletionAnimation() { @@ -208,18 +237,16 @@ public class UdfpsEnrollProgressBarSegment { return; } - Log.d(TAG, "startCompletionAnimation: mProgress = " + mProgress - + ", mOverSweepAngle = " + mOverSweepAngle); - // Reset sweep angle back to zero if the animation is being rolled back. if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { mOverSweepReverseAnimator.cancel(); mOverSweepAngle = 0f; } - // Start filling the segment if it isn't already. - if (mProgress < 1f) { + // Clear help color and start filling the segment if it isn't already. + if (mAnimatedProgress < 1f) { updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS); + updateFillColor(false /* isShowingHelp */); } // Queue the animation to run after fill completes. @@ -230,9 +257,6 @@ public class UdfpsEnrollProgressBarSegment { * Cancels (and reverses, if necessary) a queued or running completion animation. */ public void cancelCompletionAnimation() { - Log.d(TAG, "cancelCompletionAnimation: mProgress = " + mProgress - + ", mOverSweepAngle = " + mOverSweepAngle); - // Cancel the animation if it's queued or running. mHandler.removeCallbacks(mOverSweepAnimationRunnable); if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java index 6f02c64e4cf7..64b096867ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java @@ -73,24 +73,22 @@ public class UdfpsEnrollView extends UdfpsAnimationView { } void setEnrollHelper(UdfpsEnrollHelper enrollHelper) { + mFingerprintProgressDrawable.setEnrollHelper(enrollHelper); mFingerprintDrawable.setEnrollHelper(enrollHelper); } void onEnrollmentProgress(int remaining, int totalSteps) { mHandler.post(() -> { - mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps); + mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps); mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps); }); } void onEnrollmentHelp(int remaining, int totalSteps) { - mHandler.post( - () -> mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps)); + mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps)); } void onLastStepAcquired() { - mHandler.post(() -> { - mFingerprintProgressDrawable.onLastStepAcquired(); - }); + mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 888672316c8a..c128e5e2ab30 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -26,16 +26,16 @@ import android.view.MotionEvent; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.time.SystemClock; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -46,12 +46,13 @@ import java.io.PrintWriter; public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> { @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @NonNull private final DelayableExecutor mExecutor; - @NonNull private final KeyguardViewMediator mKeyguardViewMediator; @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController; @NonNull private final ConfigurationController mConfigurationController; + @NonNull private final SystemClock mSystemClock; @NonNull private final KeyguardStateController mKeyguardStateController; @NonNull private final UdfpsController mUdfpsController; + @NonNull private final UnlockedScreenOffAnimationController + mUnlockedScreenOffAnimationController; private boolean mShowingUdfpsBouncer; private boolean mUdfpsRequested; @@ -60,7 +61,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud private int mStatusBarState; private float mTransitionToFullShadeProgress; private float mLastDozeAmount; - + private long mLastUdfpsBouncerShowTime = -1; private float mStatusBarExpansion; private boolean mLaunchTransitionFadingAway; @@ -78,22 +79,22 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull StatusBar statusBar, @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull DelayableExecutor mainDelayableExecutor, @NonNull DumpManager dumpManager, - @NonNull KeyguardViewMediator keyguardViewMediator, @NonNull LockscreenShadeTransitionController transitionController, @NonNull ConfigurationController configurationController, + @NonNull SystemClock systemClock, @NonNull KeyguardStateController keyguardStateController, + @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @NonNull UdfpsController udfpsController) { super(view, statusBarStateController, statusBar, dumpManager); mKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mExecutor = mainDelayableExecutor; - mKeyguardViewMediator = keyguardViewMediator; mLockScreenShadeTransitionController = transitionController; mConfigurationController = configurationController; + mSystemClock = systemClock; mKeyguardStateController = keyguardStateController; mUdfpsController = udfpsController; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; } @Override @@ -102,6 +103,12 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } @Override + public void onInit() { + super.onInit(); + mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); + } + + @Override protected void onViewAttached() { super.onViewAttached(); final float dozeAmount = mStatusBarStateController.getDozeAmount(); @@ -124,6 +131,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this); + mUnlockedScreenOffAnimationController.addCallback(mUnlockedScreenOffCallback); } @Override @@ -140,6 +148,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) { mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null); } + mUnlockedScreenOffAnimationController.removeCallback(mUnlockedScreenOffCallback); } @Override @@ -170,6 +179,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud boolean udfpsAffordanceWasNotShowing = shouldPauseAuth(); mShowingUdfpsBouncer = show; if (mShowingUdfpsBouncer) { + mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis(); + } + if (mShowingUdfpsBouncer) { if (udfpsAffordanceWasNotShowing) { mView.animateInUdfpsBouncer(null); } @@ -237,16 +249,25 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud * If we were previously showing the udfps bouncer, hide it and instead show the regular * (pin/pattern/password) bouncer. * - * Does nothing if we weren't previously showing the udfps bouncer. + * Does nothing if we weren't previously showing the UDFPS bouncer. */ private void maybeShowInputBouncer() { - if (mShowingUdfpsBouncer) { + if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) { mKeyguardViewManager.showBouncer(true); mKeyguardViewManager.resetAlternateAuth(false); } } /** + * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside + * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password + * bouncer. + */ + private boolean hasUdfpsBouncerShownWithMinTime() { + return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200; + } + + /** * Set the progress we're currently transitioning to the full shade. 0.0f means we're not * transitioning yet, while 1.0f means we've fully dragged down. */ @@ -400,4 +421,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud updatePauseAuth(); } }; + + private final UnlockedScreenOffAnimationController.Callback mUnlockedScreenOffCallback = + (linear, eased) -> mStateListener.onDozeAmountChanged(linear, eased); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 4104e3184efd..46a03e809b06 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -16,6 +16,10 @@ package com.android.systemui.controls.ui +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.os.Bundle import android.view.View import android.view.ViewGroup @@ -23,18 +27,25 @@ import android.view.WindowInsets import android.view.WindowInsets.Type import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.management.ControlsAnimations import com.android.systemui.util.LifecycleActivity import javax.inject.Inject /** - * Displays Device Controls inside an activity + * Displays Device Controls inside an activity. This activity is available to be displayed over the + * lockscreen if the user has allowed it via + * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be + * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as + * user expectations for the activity to not continue running. */ class ControlsActivity @Inject constructor( - private val uiController: ControlsUiController + private val uiController: ControlsUiController, + private val broadcastDispatcher: BroadcastDispatcher ) : LifecycleActivity() { private lateinit var parent: ViewGroup + private lateinit var broadcastReceiver: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -62,6 +73,8 @@ class ControlsActivity @Inject constructor( WindowInsets.CONSUMED } } + + initBroadcastReceiver() } override fun onResume() { @@ -83,4 +96,25 @@ class ControlsActivity @Inject constructor( uiController.hide() } + + override fun onDestroy() { + super.onDestroy() + + broadcastDispatcher.unregisterReceiver(broadcastReceiver) + } + + private fun initBroadcastReceiver() { + broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.getAction() + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + finish() + } + } + } + + val filter = IntentFilter() + filter.addAction(Intent.ACTION_SCREEN_OFF) + broadcastDispatcher.registerReceiver(broadcastReceiver, filter) + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 567d0cb3e6cb..72da7f4f893a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -256,13 +256,13 @@ class ControlsUiControllerImpl @Inject constructor ( // Force animations when transitioning from a dialog to an activity intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) - if (keyguardStateController.isUnlocked()) { + if (keyguardStateController.isShowing()) { + activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */) + } else { activityContext.startActivity( intent, ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle() ) - } else { - activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */) } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index c97a30e6e13e..79ee6a8e8830 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -198,9 +198,11 @@ public class DependencyProvider { @SysUISingleton @Provides static ThemeOverlayApplier provideThemeOverlayManager(Context context, - @Background Executor bgExecutor, OverlayManager overlayManager, + @Background Executor bgExecutor, + @Main Executor mainExecutor, + OverlayManager overlayManager, DumpManager dumpManager) { - return new ThemeOverlayApplier(overlayManager, bgExecutor, + return new ThemeOverlayApplier(overlayManager, bgExecutor, mainExecutor, context.getString(R.string.launcher_overlayable_package), context.getString(R.string.themepicker_overlayable_package), dumpManager); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 657d9246be8f..845d7dc26ee5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -275,6 +275,13 @@ public class DozeLog implements Dumpable { } /** + * Appends sensor event dropped event to logs + */ + public void traceSensorEventDropped(int sensorEvent, String reason) { + mLogger.logSensorEventDropped(sensorEvent, reason); + } + + /** * Appends pulse dropped event to logs * @param reason why the pulse was dropped */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index fe37d49980b2..dc186182432f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -205,6 +205,15 @@ class DozeLogger @Inject constructor( }) } + fun logSensorEventDropped(sensorEvent: Int, reason: String) { + buffer.log(TAG, INFO, { + int1 = sensorEvent + str1 = reason + }, { + "SensorEvent [$int1] dropped, reason=$str1" + }) + } + fun logPulseDropped(reason: String) { buffer.log(TAG, INFO, { str1 = reason diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 8d4ac75a0748..058f37a2ef38 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -29,8 +29,8 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; -import android.view.Display; +import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.BrightnessSensor; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; @@ -74,6 +74,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi private final Optional<Sensor> mLightSensorOptional; private final WakefulnessLifecycle mWakefulnessLifecycle; private final DozeParameters mDozeParameters; + private final DockManager mDockManager; private final int[] mSensorToBrightness; private final int[] mSensorToScrimOpacity; private final int mScreenBrightnessDim; @@ -101,6 +102,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, WakefulnessLifecycle wakefulnessLifecycle, DozeParameters dozeParameters, + DockManager dockManager, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mContext = context; mDozeService = service; @@ -110,6 +112,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi mDozeParameters = dozeParameters; mDozeHost = host; mHandler = handler; + mDockManager = dockManager; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness; @@ -122,7 +125,15 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case INITIALIZED: + resetBrightnessToDefault(); + break; + case DOZE_AOD: + case DOZE_REQUEST_PULSE: + case DOZE_AOD_DOCKED: + setLightSensorEnabled(true); + break; case DOZE: + setLightSensorEnabled(false); resetBrightnessToDefault(); break; case FINISH: @@ -135,15 +146,6 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } } - @Override - public void onScreenState(int state) { - if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) { - setLightSensorEnabled(true); - } else { - setLightSensorEnabled(false); - } - } - private void onDestroy() { setLightSensorEnabled(false); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index b2db86f16104..55e6154b829d 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -456,13 +456,24 @@ public class DozeSensors { public void updateListening() { if (!mConfigured || mSensor == null) return; - if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting) - && !mRegistered) { - mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); - if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); + if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) { + if (!mRegistered) { + mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); + if (DEBUG) { + Log.d(TAG, "requestTriggerSensor[" + mSensor + + "] " + mRegistered); + } + } else { + if (DEBUG) { + Log.d(TAG, "requestTriggerSensor[" + mSensor + + "] already registered"); + } + } } else if (mRegistered) { final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); - if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt); + if (DEBUG) { + Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt); + } mRegistered = false; } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 455f3c0d6ac2..756adca724e9 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -41,6 +41,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeMachine.State; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.Assert; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -92,6 +93,7 @@ public class DozeTriggers implements DozeMachine.Part { private final BroadcastDispatcher mBroadcastDispatcher; private final AuthController mAuthController; private final DelayableExecutor mMainExecutor; + private final KeyguardStateController mKeyguardStateController; private final UiEventLogger mUiEventLogger; private long mNotificationPulseTime; @@ -179,7 +181,8 @@ public class DozeTriggers implements DozeMachine.Part { DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher, SecureSettings secureSettings, AuthController authController, @Main DelayableExecutor mainExecutor, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + KeyguardStateController keyguardStateController) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -198,6 +201,7 @@ public class DozeTriggers implements DozeMachine.Part { mAuthController = authController; mMainExecutor = mainExecutor; mUiEventLogger = uiEventLogger; + mKeyguardStateController = keyguardStateController; } @Override @@ -294,6 +298,7 @@ public class DozeTriggers implements DozeMachine.Part { proximityCheckThenCall((result) -> { if (result != null && result) { // In pocket, drop event. + mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near"); return; } if (isDoubleTap || isTap) { @@ -302,6 +307,10 @@ public class DozeTriggers implements DozeMachine.Part { } gentleWakeUp(pulseReason); } else if (isPickup) { + if (shouldDropPickupEvent()) { + mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded"); + return; + } gentleWakeUp(pulseReason); } else if (isUdfpsLongPress) { final State state = mMachine.getState(); @@ -320,7 +329,7 @@ public class DozeTriggers implements DozeMachine.Part { }, true /* alreadyPerformedProxCheck */, pulseReason); } - if (isPickup) { + if (isPickup && !shouldDropPickupEvent()) { final long timeSinceNotification = SystemClock.elapsedRealtime() - mNotificationPulseTime; final boolean withinVibrationThreshold = @@ -329,6 +338,10 @@ public class DozeTriggers implements DozeMachine.Part { } } + private boolean shouldDropPickupEvent() { + return mKeyguardStateController.isOccluded(); + } + private void gentleWakeUp(int reason) { // Log screen wake up reason (lift/pickup, tap, double-tap) Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason)) diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index bfa478088cad..5b327bd5e4c1 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt @@ -34,7 +34,7 @@ import javax.inject.Inject * See [DumpHandler] for more information on how and when this information is dumped. */ @SysUISingleton -class DumpManager @Inject constructor() { +open class DumpManager @Inject constructor() { private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap() private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap() diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt index 28f63b07e584..6561bd5a5323 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt @@ -25,8 +25,12 @@ import javax.inject.Inject * Proxy to make {@link SystemProperties} easily testable. */ @SysUISingleton -class SystemPropertiesHelper @Inject constructor() { +open class SystemPropertiesHelper @Inject constructor() { fun getBoolean(name: String, default: Boolean): Boolean { return SystemProperties.getBoolean(name, default) } + + fun set(name: String, value: Int) { + SystemProperties.set(name, value.toString()) + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index bc4ced452630..1b4a47e4bf39 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -62,6 +62,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.view.RotationPolicy; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; @@ -172,7 +173,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite SysUiState sysUiState, @Main Handler handler, PackageManager packageManager, - StatusBar statusBar) { + StatusBar statusBar, + KeyguardUpdateMonitor keyguardUpdateMonitor) { super(context, windowManagerFuncs, @@ -204,7 +206,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite sysUiState, handler, packageManager, - statusBar); + statusBar, + keyguardUpdateMonitor); mLockPatternUtils = lockPatternUtils; mKeyguardStateController = keyguardStateController; @@ -266,7 +269,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite this::getWalletViewController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(), - getStatusBar()); + getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils); if (shouldShowLockMessage(dialog)) { dialog.showLockMessage(); @@ -334,12 +337,13 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, - StatusBar statusBar) { + StatusBar statusBar, KeyguardUpdateMonitor keyguardUpdateMonitor, + LockPatternUtils lockPatternUtils) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions, adapter, overflowAdapter, sysuiColorExtractor, statusBarService, notificationShadeWindowController, sysuiState, onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null, - statusBar); + statusBar, keyguardUpdateMonitor, lockPatternUtils); mWalletFactory = walletFactory; // Update window attributes diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 06e74821869e..9ada54b9ef6f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -84,6 +84,7 @@ import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ImageView.ScaleType; @@ -108,6 +109,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.ScreenshotHelper; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; import com.android.systemui.animation.Interpolators; @@ -170,6 +172,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; + // See NotificationManagerService#scheduleDurationReachedLocked + private static final long TOAST_FADE_TIME = 333; + // See NotificationManagerService.LONG_DELAY + private static final int TOAST_VISIBLE_TIME = 3500; + private final Context mContext; private final GlobalActionsManager mWindowManagerFuncs; private final AudioManager mAudioManager; @@ -231,6 +238,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected Handler mMainHandler; private int mSmallestScreenWidthDp; private final StatusBar mStatusBar; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -338,7 +346,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene SysUiState sysUiState, @Main Handler handler, PackageManager packageManager, - StatusBar statusBar) { + StatusBar statusBar, + KeyguardUpdateMonitor keyguardUpdateMonitor) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -369,6 +378,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mMainHandler = handler; mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; mStatusBar = statusBar; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -422,6 +432,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return mStatusBar; } + protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() { + return mKeyguardUpdateMonitor; + } + /** * Show the global actions dialog (creating if necessary) * @@ -653,7 +667,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger, - mInfoProvider, mStatusBar); + mInfoProvider, mStatusBar, mKeyguardUpdateMonitor, mLockPatternUtils); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); @@ -2122,6 +2136,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private GlobalActionsInfoProvider mInfoProvider; private GestureDetector mGestureDetector; private StatusBar mStatusBar; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private LockPatternUtils mLockPatternUtils; protected ViewGroup mContainer; @@ -2173,7 +2189,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, - @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) { + @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar, + KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) { super(context, themeRes); mContext = context; mAdapter = adapter; @@ -2188,6 +2205,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mUiEventLogger = uiEventLogger; mInfoProvider = infoProvider; mStatusBar = statusBar; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLockPatternUtils = lockPatternUtils; mGestureDetector = new GestureDetector(mContext, mGestureListener); @@ -2308,6 +2327,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene if (mInfoProvider != null && mInfoProvider.shouldShowMessage()) { mInfoProvider.addPanel(mContext, mContainer, mAdapter.getCount(), () -> dismiss()); } + + // If user entered from the lock screen and smart lock was enabled, disable it + int user = KeyguardUpdateMonitor.getCurrentUser(); + boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user); + if (mKeyguardShowing && userHasTrust) { + mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser()); + showSmartLockDisabledMessage(); + } } protected void fixNavBarClipping() { @@ -2319,6 +2346,37 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene contentParent.setClipToPadding(false); } + private void showSmartLockDisabledMessage() { + // Since power menu is the top window, make a Toast-like view that will show up + View message = LayoutInflater.from(mContext) + .inflate(com.android.systemui.R.layout.global_actions_toast, mContainer, false); + + // Set up animation + AccessibilityManager mAccessibilityManager = + (AccessibilityManager) getContext().getSystemService( + Context.ACCESSIBILITY_SERVICE); + final int visibleTime = mAccessibilityManager.getRecommendedTimeoutMillis( + TOAST_VISIBLE_TIME, AccessibilityManager.FLAG_CONTENT_TEXT); + message.setVisibility(View.VISIBLE); + message.setAlpha(0f); + mContainer.addView(message); + + // Fade in + message.animate() + .alpha(1f) + .setDuration(TOAST_FADE_TIME) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Then fade out + message.animate() + .alpha(0f) + .setDuration(TOAST_FADE_TIME) + .setStartDelay(visibleTime); + } + }); + } + @Override protected void onStart() { super.onStart(); diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java index d30783c29f92..a51ec54ebcce 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -102,7 +102,6 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { @Override public Size reportSurfaceSize() { - mTexture.use(null /* consumer */); mSurfaceSize.set(mTexture.getTextureDimensions()); return new Size(mSurfaceSize.width(), mSurfaceSize.height()); } @@ -124,6 +123,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { private final WallpaperManager mWallpaperManager; private Bitmap mBitmap; private boolean mWcgContent; + private boolean mTextureUsed; private WallpaperTexture(WallpaperManager wallpaperManager) { mWallpaperManager = wallpaperManager; @@ -141,6 +141,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { mWallpaperManager.forgetLoadedWallpaper(); if (mBitmap != null) { mDimensions.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); + mTextureUsed = true; } else { Log.w(TAG, "Can't get bitmap"); } @@ -171,6 +172,9 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { } private Rect getTextureDimensions() { + if (!mTextureUsed) { + mDimensions.set(mWallpaperManager.peekBitmapDimensions()); + } return mDimensions; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index 2d215e0f1f62..a424674ed252 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -100,8 +100,7 @@ public class KeyguardIndicationRotateTextViewController extends */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean updateImmediately) { - if (type == INDICATION_TYPE_NOW_PLAYING - || type == INDICATION_TYPE_REVERSE_CHARGING) { + if (type == INDICATION_TYPE_REVERSE_CHARGING) { // temporarily don't show here, instead use AmbientContainer b/181049781 return; } @@ -303,7 +302,6 @@ public class KeyguardIndicationRotateTextViewController extends public static final int INDICATION_TYPE_TRUST = 6; public static final int INDICATION_TYPE_RESTING = 7; public static final int INDICATION_TYPE_USER_LOCKED = 8; - public static final int INDICATION_TYPE_NOW_PLAYING = 9; public static final int INDICATION_TYPE_REVERSE_CHARGING = 10; @IntDef({ @@ -317,7 +315,6 @@ public class KeyguardIndicationRotateTextViewController extends INDICATION_TYPE_TRUST, INDICATION_TYPE_RESTING, INDICATION_TYPE_USER_LOCKED, - INDICATION_TYPE_NOW_PLAYING, INDICATION_TYPE_REVERSE_CHARGING, }) @Retention(RetentionPolicy.SOURCE) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5751c0fddde0..926b4cc94c6a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2827,7 +2827,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, } } - private void setShowingLocked(boolean showing) { + void setShowingLocked(boolean showing) { setShowingLocked(showing, false /* forceCallbacks */); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 2facf3dedd18..c743fe125cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -212,14 +212,32 @@ class MediaCarouselController @Inject constructor( isSsReactivated: Boolean ) { if (addOrUpdatePlayer(key, oldKey, data)) { + // Log card received if a new resumable media card is added MediaPlayerData.getMediaPlayer(key)?.let { logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, + it.mUid, /* isRecommendationCard */ false, it.surfaceForSmartspaceLogging, rank = MediaPlayerData.getMediaPlayerIndex(key)) } } + if (isSsReactivated) { + // If resumable media is reactivated by headphone connection, update instance + // id for each card and log a receive event. + MediaPlayerData.players().forEachIndexed { index, it -> + if (it.recommendationViewHolder == null) { + it.mInstanceId = SmallHash.hash(it.mUid + + systemClock.currentTimeMillis().toInt()) + logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + it.mInstanceId, + it.mUid, + /* isRecommendationCard */ false, + it.surfaceForSmartspaceLogging, + rank = index) + } + } + } if (mediaCarouselScrollHandler.visibleToUser && isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) { // It could happen that reactived media player isn't visible to user because @@ -252,6 +270,7 @@ class MediaCarouselController @Inject constructor( MediaPlayerData.getMediaPlayer(key)?.let { logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, + it.mUid, /* isRecommendationCard */ true, it.surfaceForSmartspaceLogging, rank = MediaPlayerData.getMediaPlayerIndex(key)) @@ -261,6 +280,7 @@ class MediaCarouselController @Inject constructor( MediaPlayerData.getMediaPlayerIndex(key)) { logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN it.mInstanceId, + it.mUid, /* isRecommendationCard */ true, it.surfaceForSmartspaceLogging) } @@ -339,9 +359,9 @@ class MediaCarouselController @Inject constructor( if (activeMediaIndex != -1) { previousVisiblePlayerKey?.let { val previousVisibleIndex = MediaPlayerData.playerKeys() - .indexOfFirst { key -> it == key } + .indexOfFirst { key -> it == key } mediaCarouselScrollHandler - .scrollToPlayer(previousVisibleIndex, activeMediaIndex) + .scrollToPlayer(previousVisibleIndex, activeMediaIndex) } ?: { mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex) } @@ -355,11 +375,11 @@ class MediaCarouselController @Inject constructor( MediaPlayerData.moveIfExists(oldKey, key) val existingPlayer = MediaPlayerData.getMediaPlayer(key) val curVisibleMediaKey = MediaPlayerData.playerKeys() - .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) if (existingPlayer == null) { var newPlayer = mediaControlPanelFactory.get() newPlayer.attachPlayer( - PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) + PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -407,14 +427,14 @@ class MediaCarouselController @Inject constructor( var newRecs = mediaControlPanelFactory.get() newRecs.attachRecommendation( - RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)) + RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)) newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT) + ViewGroup.LayoutParams.WRAP_CONTENT) newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) newRecs.bindRecommendation(data.copy(backgroundColor = bgColor)) val curVisibleMediaKey = MediaPlayerData.playerKeys() - .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize, systemClock) updatePlayerToState(newRecs, noAnimation = true) reorderAllPlayers(curVisibleMediaKey) @@ -462,7 +482,7 @@ class MediaCarouselController @Inject constructor( removePlayer(key, dismissMediaData = false, dismissRecommendation = false) smartspaceMediaData?.let { addSmartspaceMediaRecommendations( - it.targetId, it, MediaPlayerData.shouldPrioritizeSs) + it.targetId, it, MediaPlayerData.shouldPrioritizeSs) } } else { removePlayer(key, dismissMediaData = false, dismissRecommendation = false) @@ -585,7 +605,7 @@ class MediaCarouselController @Inject constructor( ?: endShowsActive if (currentlyShowingOnlyActive != endShowsActive || ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) && - startShowsActive != endShowsActive)) { + startShowsActive != endShowsActive)) { // Whenever we're transitioning from between differing states or the endstate differs // we reset the translation currentlyShowingOnlyActive = endShowsActive @@ -696,23 +716,43 @@ class MediaCarouselController @Inject constructor( } logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN mediaControlPanel.mInstanceId, + mediaControlPanel.mUid, isRecommendationCard, mediaControlPanel.surfaceForSmartspaceLogging) } } @JvmOverloads + /** + * Log Smartspace events + * + * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN) + * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new + * instanceId + * @param uid uid for the application that media comes from + * @param isRecommendationCard whether the card is media recommendation + * @param surface which display surface the media card is on (e.g. lockscreen, shade) + * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1 + * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc. + * @param interactedSubcardCardinality how many media items were shown to the user when there + * is user interaction + * @param rank the rank for media card in the media carousel, starting from 0 + * + */ fun logSmartspaceCardReported( eventId: Int, instanceId: Int, + uid: Int, isRecommendationCard: Boolean, surface: Int, + interactedSubcardRank: Int = 0, + interactedSubcardCardinality: Int = 0, rank: Int = mediaCarouselScrollHandler.visibleMediaIndex ) { // Only log media resume card when Smartspace data is available if (!isRecommendationCard && - !mediaManager.smartspaceMediaData.isActive && - MediaPlayerData.smartspaceMediaData == null) { + !mediaManager.smartspaceMediaData.isActive && + MediaPlayerData.smartspaceMediaData == null) { return } @@ -720,13 +760,21 @@ class MediaCarouselController @Inject constructor( SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, eventId, instanceId, - if (isRecommendationCard) - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS - else - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA, + // Deprecated, replaced with AiAi feature type so we don't need to create logging + // card type for each new feature. + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, surface, rank, - mediaContent.getChildCount()) + mediaContent.getChildCount(), + if (isRecommendationCard) + 15 // MEDIA_RECOMMENDATION + else + 31, // MEDIA_RESUME + uid, + interactedSubcardRank, + interactedSubcardCardinality, + 0 // received_latency_millis + ) /* ktlint-disable max-line-length */ } @@ -738,18 +786,20 @@ class MediaCarouselController @Inject constructor( if (!recommendation.isEmpty()) { logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS recommendation.get(0).mInstanceId, + recommendation.get(0).mUid, true, recommendation.get(0).surfaceForSmartspaceLogging, - /* rank */-1) + rank = -1) } else { val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { val player = MediaPlayerData.players().elementAt(visibleMediaIndex) logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS player.mInstanceId, - false, + player.mUid, + false, player.surfaceForSmartspaceLogging, - /* rank */-1) + rank = -1) } } mediaManager.onSwipeToDismiss() @@ -768,7 +818,7 @@ class MediaCarouselController @Inject constructor( @VisibleForTesting internal object MediaPlayerData { private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null, - emptyList(), emptyList(), "INVALID", null, null, null, true, null) + emptyList(), emptyList(), "INVALID", null, null, null, true, null) // Whether should prioritize Smartspace card. internal var shouldPrioritizeSs: Boolean = false private set @@ -776,18 +826,18 @@ internal object MediaPlayerData { private set data class MediaSortKey( - // Whether the item represents a Smartspace media recommendation. + // Whether the item represents a Smartspace media recommendation. val isSsMediaRec: Boolean, val data: MediaData, val updateTime: Long = 0 ) private val comparator = - compareByDescending<MediaSortKey> { it.data.isPlaying } - .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec } - .thenByDescending { it.data.isLocalSession } - .thenByDescending { !it.data.resumption } - .thenByDescending { it.updateTime } + compareByDescending<MediaSortKey> { it.data.isPlaying } + .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec } + .thenByDescending { it.data.isLocalSession } + .thenByDescending { !it.data.resumption } + .thenByDescending { it.updateTime } private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator) private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf() @@ -808,7 +858,8 @@ internal object MediaPlayerData { ) { shouldPrioritizeSs = shouldPrioritize removeMediaPlayer(key) - val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, clock.currentTimeMillis()) + val sortKey = MediaSortKey(/* isSsMediaRec= */ true, + EMPTY.copy(isPlaying = false), clock.currentTimeMillis()) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) smartspaceMediaData = data @@ -888,4 +939,4 @@ internal object MediaPlayerData { } return false } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 15a70831b2f9..e7445f920ffe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -33,6 +33,7 @@ import android.graphics.drawable.Icon; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; +import android.os.Process; import android.text.Layout; import android.util.Log; import android.view.View; @@ -74,6 +75,8 @@ public class MediaControlPanel { private static final String TAG = "MediaControlPanel"; private static final float DISABLED_ALPHA = 0.38f; + private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google" + + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity"; private static final String EXTRAS_SMARTSPACE_INTENT = "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"; private static final int MEDIA_RECOMMENDATION_ITEMS_PER_ROW = 3; @@ -111,6 +114,9 @@ public class MediaControlPanel { private int mAlbumArtSize; // Instance id for logging purpose. protected int mInstanceId = -1; + // Uid for the media app. + protected int mUid = Process.INVALID_UID; + private int mSmartspaceMediaItemsCount; private MediaCarouselController mMediaCarouselController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; @@ -248,7 +254,8 @@ public class MediaControlPanel { openGuts(); return true; } else { - return false; + closeGuts(); + return true; } }); mRecommendationViewHolder.getCancel().setOnClickListener(v -> { @@ -266,7 +273,13 @@ public class MediaControlPanel { } mKey = key; MediaSession.Token token = data.getToken(); - mInstanceId = SmallHash.hash(data.getPackageName()); + PackageManager packageManager = mContext.getPackageManager(); + try { + mUid = packageManager.getApplicationInfo(data.getPackageName(), 0 /* flags */).uid; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to look up package name", e); + } + mInstanceId = SmallHash.hash(mUid); mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { @@ -360,27 +373,16 @@ public class MediaControlPanel { final MediaDeviceData device = data.getDevice(); final int seamlessId = mPlayerViewHolder.getSeamless().getId(); - final int seamlessFallbackId = mPlayerViewHolder.getSeamlessFallback().getId(); - final boolean showFallback = device != null && !device.getEnabled(); - final int seamlessFallbackVisibility = showFallback ? View.VISIBLE : View.GONE; - mPlayerViewHolder.getSeamlessFallback().setVisibility(seamlessFallbackVisibility); - expandedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility); - collapsedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility); - final int seamlessVisibility = showFallback ? View.GONE : View.VISIBLE; - mPlayerViewHolder.getSeamless().setVisibility(seamlessVisibility); - expandedSet.setVisibility(seamlessId, seamlessVisibility); - collapsedSet.setVisibility(seamlessId, seamlessVisibility); - final float seamlessAlpha = data.getResumption() ? DISABLED_ALPHA : 1.0f; + // Disable clicking on output switcher for invalid devices and resumption controls + final boolean seamlessDisabled = (device != null && !device.getEnabled()) + || data.getResumption(); + final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f; expandedSet.setAlpha(seamlessId, seamlessAlpha); collapsedSet.setAlpha(seamlessId, seamlessAlpha); - // Disable clicking on output switcher for resumption controls. - mPlayerViewHolder.getSeamless().setEnabled(!data.getResumption()); + mPlayerViewHolder.getSeamless().setEnabled(!seamlessDisabled); String deviceString = null; - if (showFallback) { - iconView.setImageDrawable(null); - } else if (device != null) { + if (device != null && device.getEnabled()) { Drawable icon = device.getIcon(); - iconView.setVisibility(View.VISIBLE); if (icon instanceof AdaptiveIcon) { AdaptiveIcon aIcon = (AdaptiveIcon) icon; aIcon.setBackgroundColor(mBackgroundColor); @@ -391,10 +393,9 @@ public class MediaControlPanel { deviceString = device.getName(); } else { // Reset to default - Log.w(TAG, "device is null. Not binding output chip."); - iconView.setVisibility(View.GONE); - deviceString = mContext.getString( - com.android.internal.R.string.ext_media_seamless_action); + Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip."); + iconView.setImageResource(R.drawable.ic_media_home_devices); + deviceString = mContext.getString(R.string.media_seamless_other_device); } deviceName.setText(deviceString); seamlessView.setContentDescription(deviceString); @@ -531,10 +532,11 @@ public class MediaControlPanel { } // Set up recommendation card's header. - ApplicationInfo applicationInfo = null; + ApplicationInfo applicationInfo; try { applicationInfo = mContext.getPackageManager() .getApplicationInfo(data.getPackageName(), 0 /* flags */); + mUid = applicationInfo.uid; } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Fail to get media recommendation's app info", e); return; @@ -553,7 +555,8 @@ public class MediaControlPanel { headerTitleText.setText(appLabel); } // Set up media rec card's tap action if applicable. - setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction()); + setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(), + /* interactedSubcardRank */ -1); // Set up media rec card's accessibility label. recommendationCard.setContentDescription( mContext.getString(R.string.controls_media_smartspace_rec_description, appLabel)); @@ -567,7 +570,8 @@ public class MediaControlPanel { ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); int mediaRecommendationNum = Math.min(mediaRecommendationList.size(), MEDIA_RECOMMENDATION_MAX_NUM); - for (int itemIndex = 0, uiComponentIndex = 0; + int uiComponentIndex = 0; + for (int itemIndex = 0; itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum; itemIndex++) { SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex); @@ -582,7 +586,16 @@ public class MediaControlPanel { // Set up the media item's click listener if applicable. ViewGroup mediaCoverContainer = mediaCoverContainers.get(uiComponentIndex); - setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation); + setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, + uiComponentIndex); + // Bubble up the long-click event to the card. + mediaCoverContainer.setOnLongClickListener(v -> { + View parent = (View) v.getParent(); + if (parent != null) { + parent.performLongClick(); + } + return true; + }); // Set up the accessibility label for the media item. String artistName = recommendation.getExtras() @@ -614,10 +627,10 @@ public class MediaControlPanel { mediaCoverItemsResIds.get(uiComponentIndex), true); setVisibleAndAlpha(expandedSet, mediaCoverContainersResIds.get(uiComponentIndex), true); - uiComponentIndex++; } + mSmartspaceMediaItemsCount = uiComponentIndex; // Set up long press to show guts setting panel. mRecommendationViewHolder.getDismiss().setOnClickListener(v -> { logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS @@ -625,6 +638,22 @@ public class MediaControlPanel { closeGuts(); mMediaDataManagerLazy.get().dismissSmartspaceRecommendation( data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L); + + Intent dismissIntent = data.getDismissIntent(); + if (dismissIntent == null) { + Log.w(TAG, "Cannot create dismiss action click action: " + + "extras missing dismiss_intent."); + return; + } + + if (dismissIntent.getComponent() != null + && dismissIntent.getComponent().getClassName() + .equals(EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)) { + // Dismiss the card Smartspace data through Smartspace trampoline activity. + mContext.startActivity(dismissIntent); + } else { + mContext.sendBroadcast(dismissIntent); + } }); mController = null; @@ -750,7 +779,8 @@ public class MediaControlPanel { private void setSmartspaceRecItemOnClickListener( @NonNull View view, - @NonNull SmartspaceAction action) { + @NonNull SmartspaceAction action, + int interactedSubcardRank) { if (view == null || action == null || action.getIntent() == null || action.getIntent().getExtras() == null) { Log.e(TAG, "No tap action can be set up"); @@ -758,9 +788,10 @@ public class MediaControlPanel { } view.setOnClickListener(v -> { - // When media recommendation card is shown, it will always be the top card. logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK - /* isRecommendationCard */ true); + /* isRecommendationCard */ true, + interactedSubcardRank, + getSmartspaceSubCardCardinality()); if (shouldSmartspaceRecItemOpenInForeground(action)) { // Request to unlock the device if the activity needs to be opened in foreground. @@ -818,9 +849,28 @@ public class MediaControlPanel { } private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) { + logSmartspaceCardReported(eventId, isRecommendationCard, + /* interactedSubcardRank */ 0, + /* interactedSubcardCardinality */ 0); + } + + private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard, + int interactedSubcardRank, int interactedSubcardCardinality) { mMediaCarouselController.logSmartspaceCardReported(eventId, mInstanceId, + mUid, isRecommendationCard, - getSurfaceForSmartspaceLogging()); + getSurfaceForSmartspaceLogging(), + interactedSubcardRank, + interactedSubcardCardinality); + } + + private int getSmartspaceSubCardCardinality() { + if (!mMediaCarouselController.getMediaCarouselScrollHandler().getQsExpanded() + && mSmartspaceMediaItemsCount > 3) { + return 3; + } + + return mSmartspaceMediaItemsCount; } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index c8deb014f781..79206e86da09 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -16,6 +16,7 @@ package com.android.systemui.media +import android.content.Context import android.os.SystemProperties import android.util.Log import com.android.internal.annotations.VisibleForTesting @@ -32,6 +33,8 @@ import kotlin.collections.LinkedHashMap private const val TAG = "MediaDataFilter" private const val DEBUG = true +private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = ("com.google" + + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity") private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds" /** @@ -51,6 +54,7 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties * background users (e.g. timeouts). */ class MediaDataFilter @Inject constructor( + private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, private val mediaResumeListener: MediaResumeListener, private val lockscreenUserManager: NotificationLockscreenUserManager, @@ -229,6 +233,18 @@ class MediaDataFilter @Inject constructor( mediaDataManager.setTimedOut(it, timedOut = true, forceUpdate = true) } if (smartspaceMediaData.isActive) { + val dismissIntent = smartspaceMediaData.dismissIntent + if (dismissIntent == null) { + Log.w(TAG, "Cannot create dismiss action click action: " + + "extras missing dismiss_intent.") + } else if (dismissIntent.getComponent() != null && + dismissIntent.getComponent().getClassName() + == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) { + // Dismiss the card Smartspace data through Smartspace trampoline activity. + context.startActivity(dismissIntent) + } else { + context.sendBroadcast(dismissIntent) + } smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 0a28b47923da..eacdab6e537d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -76,12 +76,13 @@ private val ART_URIS = arrayOf( private const val TAG = "MediaDataManager" private const val DEBUG = true +private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent" private val LOADING = MediaData(-1, false, 0, null, null, null, null, null, emptyList(), emptyList(), "INVALID", null, null, null, true, null) @VisibleForTesting internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false, - "INVALID", null, emptyList(), 0) + "INVALID", null, emptyList(), null, 0) fun isMediaNotification(sbn: StatusBarNotification): Boolean { if (!sbn.notification.hasMediaSession()) { @@ -212,8 +213,8 @@ class MediaDataManager( mediaDataCombineLatest.addListener(mediaDataFilter) // Set up links back into the pipeline for listeners that need to send events upstream. - mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean -> - setTimedOut(token, timedOut) } + mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean -> + setTimedOut(key, timedOut) } mediaResumeListener.setManager(this) mediaDataFilter.mediaDataManager = this @@ -414,14 +415,18 @@ class MediaDataManager( * This will make the player not active anymore, hiding it from QQS and Keyguard. * @see MediaData.active */ - internal fun setTimedOut(token: String, timedOut: Boolean, forceUpdate: Boolean = false) { - mediaEntries[token]?.let { + internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) { + mediaEntries[key]?.let { if (it.active == !timedOut && !forceUpdate) { + if (it.resumption) { + if (DEBUG) Log.d(TAG, "timing out resume player $key") + dismissMediaData(key, 0L /* delay */) + } return } it.active = !timedOut - if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut") - onMediaDataLoaded(token, token, it) + if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut") + onMediaDataLoaded(key, key, it) } } @@ -879,12 +884,22 @@ class MediaDataManager( target: SmartspaceTarget, isActive: Boolean ): SmartspaceMediaData { + var dismissIntent: Intent? = null + if (target.baseAction != null && target.baseAction.extras != null) { + dismissIntent = target + .baseAction + .extras + .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent + } packageName(target)?.let { return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it, - target.baseAction, target.iconGrid, 0) + target.baseAction, target.iconGrid, + dismissIntent, 0) } return EMPTY_SMARTSPACE_MEDIA_DATA - .copy(targetId = target.smartspaceTargetId, isActive = isActive) + .copy(targetId = target.smartspaceTargetId, + isActive = isActive, + dismissIntent = dismissIntent) } private fun packageName(target: SmartspaceTarget): String? { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index ab568c8c5a85..608c784f5d39 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -35,6 +35,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils +import com.android.systemui.util.time.SystemClock import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.ConcurrentLinkedQueue @@ -53,11 +54,13 @@ class MediaResumeListener @Inject constructor( @Background private val backgroundExecutor: Executor, private val tunerService: TunerService, private val mediaBrowserFactory: ResumeMediaBrowserFactory, - dumpManager: DumpManager + dumpManager: DumpManager, + private val systemClock: SystemClock ) : MediaDataManager.Listener, Dumpable { private var useMediaResumption: Boolean = Utils.useMediaResumption(context) - private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue() + private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> = + ConcurrentLinkedQueue() private lateinit var mediaDataManager: MediaDataManager @@ -131,14 +134,32 @@ class MediaResumeListener @Inject constructor( val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null) val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex()) ?.dropLastWhile { it.isEmpty() } + var needsUpdate = false components?.forEach { val info = it.split("/") val packageName = info[0] val className = info[1] val component = ComponentName(packageName, className) - resumeComponents.add(component) + + val lastPlayed = if (info.size == 3) { + try { + info[2].toLong() + } catch (e: NumberFormatException) { + needsUpdate = true + systemClock.currentTimeMillis() + } + } else { + needsUpdate = true + systemClock.currentTimeMillis() + } + resumeComponents.add(component to lastPlayed) } Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}") + + if (needsUpdate) { + // Save any missing times that we had to fill in + writeSharedPrefs() + } } /** @@ -149,9 +170,12 @@ class MediaResumeListener @Inject constructor( return } + val now = systemClock.currentTimeMillis() resumeComponents.forEach { - val browser = mediaBrowserFactory.create(mediaBrowserCallback, it) - browser.findRecentMedia() + if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) { + val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first) + browser.findRecentMedia() + } } } @@ -234,18 +258,24 @@ class MediaResumeListener @Inject constructor( */ private fun updateResumptionList(componentName: ComponentName) { // Remove if exists - resumeComponents.remove(componentName) + resumeComponents.remove(resumeComponents.find { it.first.equals(componentName) }) // Insert at front of queue - resumeComponents.add(componentName) + val currentTime = systemClock.currentTimeMillis() + resumeComponents.add(componentName to currentTime) // Remove old components if over the limit if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) { resumeComponents.remove() } - // Save changes + writeSharedPrefs() + } + + private fun writeSharedPrefs() { val sb = StringBuilder() resumeComponents.forEach { - sb.append(it.flattenToString()) + sb.append(it.first.flattenToString()) + sb.append("/") + sb.append(it.second) sb.append(ResumeMediaBrowser.DELIMITER) } val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 9a3919326cbd..6f047712c954 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -20,6 +20,7 @@ import android.media.session.MediaController import android.media.session.PlaybackState import android.os.SystemProperties import android.util.Log +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState @@ -29,9 +30,15 @@ import javax.inject.Inject private const val DEBUG = true private const val TAG = "MediaTimeout" -private val PAUSED_MEDIA_TIMEOUT = SystemProperties + +@VisibleForTesting +val PAUSED_MEDIA_TIMEOUT = SystemProperties .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10)) +@VisibleForTesting +val RESUME_MEDIA_TIMEOUT = SystemProperties + .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3)) + /** * Controller responsible for keeping track of playback states and expiring inactive streams. */ @@ -45,8 +52,9 @@ class MediaTimeoutListener @Inject constructor( /** * Callback representing that a media object is now expired: - * @param token Media session unique identifier - * @param pauseTimeout True when expired for {@code PAUSED_MEDIA_TIMEOUT} + * @param key Media control unique identifier + * @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media, + * or {@code RESUME_MEDIA_TIMEOUT} for resume media */ lateinit var timeoutCallback: (String, Boolean) -> Unit @@ -122,6 +130,7 @@ class MediaTimeoutListener @Inject constructor( var timedOut = false var playing: Boolean? = null + var resumption: Boolean? = null var destroyed = false var mediaData: MediaData = data @@ -159,12 +168,19 @@ class MediaTimeoutListener @Inject constructor( } override fun onSessionDestroyed() { - // If the session is destroyed, the controller is no longer valid, and we will need to - // recreate it if this key is updated later if (DEBUG) { Log.d(TAG, "Session destroyed for $key") } - destroy() + + if (resumption == true) { + // Some apps create a session when MBS is queried. We should unregister the + // controller since it will no longer be valid, but don't cancel the timeout + mediaController?.unregisterCallback(this) + } else { + // For active controls, if the session is destroyed, clean up everything since we + // will need to recreate it if this key is updated later + destroy() + } } private fun processState(state: PlaybackState?, dispatchEvents: Boolean) { @@ -173,20 +189,28 @@ class MediaTimeoutListener @Inject constructor( } val isPlaying = state != null && isPlayingState(state.state) - if (playing == isPlaying && playing != null) { + val resumptionChanged = resumption != mediaData.resumption + if (playing == isPlaying && playing != null && !resumptionChanged) { return } playing = isPlaying + resumption = mediaData.resumption if (!isPlaying) { if (DEBUG) { - Log.v(TAG, "schedule timeout for $key") + Log.v(TAG, "schedule timeout for $key playing $isPlaying, $resumption") } - if (cancellation != null) { + if (cancellation != null && !resumptionChanged) { + // if the media changed resume state, we'll need to adjust the timeout length if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.") return } - expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state") + expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption") + val timeout = if (mediaData.resumption) { + RESUME_MEDIA_TIMEOUT + } else { + PAUSED_MEDIA_TIMEOUT + } cancellation = mainExecutor.executeDelayed({ cancellation = null if (DEBUG) { @@ -195,7 +219,7 @@ class MediaTimeoutListener @Inject constructor( timedOut = true // this event is async, so it's safe even when `dispatchEvents` is false timeoutCallback(key, timedOut) - }, PAUSED_MEDIA_TIMEOUT) + }, timeout) } else { expireMediaTimeout(key, "playback started - $state, $key") timedOut = false diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt index 791f59d3ae13..35603b6ef6cc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt @@ -43,7 +43,6 @@ class PlayerViewHolder private constructor(itemView: View) { val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless) val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image) val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text) - val seamlessFallback = itemView.requireViewById<ImageView>(R.id.media_seamless_fallback) // Seek bar val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar) @@ -124,7 +123,6 @@ class PlayerViewHolder private constructor(itemView: View) { R.id.header_title, R.id.header_artist, R.id.media_seamless, - R.id.media_seamless_fallback, R.id.notification_media_progress_time, R.id.media_progress_bar, R.id.action0, diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index 9ac128920edb..61fdefd4579f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -17,6 +17,7 @@ package com.android.systemui.media import android.app.smartspace.SmartspaceAction +import android.content.Intent /** State of a Smartspace media recommendations view. */ data class SmartspaceMediaData( @@ -45,6 +46,10 @@ data class SmartspaceMediaData( */ val recommendations: List<SmartspaceAction>, /** + * Intent for the user's initiated dismissal. + */ + val dismissIntent: Intent?, + /** * View's background color. */ val backgroundColor: Int diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt index b6c2ef1db82a..140a1fef93f7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt @@ -1,10 +1,13 @@ package com.android.systemui.media import android.app.smartspace.SmartspaceTarget +import android.util.Log import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import javax.inject.Inject +private const val TAG = "SsMediaDataProvider" + /** Provides SmartspaceTargets of media types for SystemUI media control. */ class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin { @@ -31,6 +34,10 @@ class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin } } + if (!mediaTargets.isEmpty()) { + Log.d(TAG, "Forwarding Smartspace media updates $mediaTargets") + } + smartspaceMediaTargets = mediaTargets smartspaceMediaTargetListeners.forEach { it.onSmartspaceTargetsUpdated(smartspaceMediaTargets) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index cb11454e44ee..8e6eb02bb6f6 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -410,6 +410,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, new Handler(Looper.getMainLooper())) { @Override public void onChange(boolean selfChange, Uri uri) { + // TODO(b/198002034): Content observers currently can still be called back after being + // unregistered, and in this case we can ignore the change if the nav bar has been + // destroyed already + if (mNavigationBarView == null) { + return; + } updateAssistantEntrypoints(); } }; @@ -601,6 +607,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } public void destroyView() { + setAutoHideController(/* autoHideController */ null); mCommandQueue.removeCallback(this); mContext.getSystemService(WindowManager.class).removeViewImmediate( mNavigationBarView.getRootView()); @@ -651,7 +658,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, if (mIsOnDefaultDisplay) { final RotationButtonController rotationButtonController = mNavigationBarView.getRotationButtonController(); - rotationButtonController.addRotationCallback(mRotationWatcher); + rotationButtonController.setRotationCallback(mRotationWatcher); // Reset user rotation pref to match that of the WindowManager if starting in locked // mode. This will automatically happen when switching from auto-rotate to locked mode. @@ -690,6 +697,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onViewDetachedFromWindow(View v) { + final RotationButtonController rotationButtonController = + mNavigationBarView.getRotationButtonController(); + rotationButtonController.setRotationCallback(null); mNavigationBarView.getBarTransitions().destroy(); mNavigationBarView.getLightTransitionsController().destroy(mContext); mOverviewProxyService.removeCallback(mOverviewProxyListener); @@ -937,6 +947,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onRotationProposal(final int rotation, boolean isValid) { + // The CommandQueue callbacks are added when the view is created to ensure we track other + // states, but until the view is attached (at the next traversal), the view's display is + // not valid. Just ignore the rotation in this case. + if (!mNavigationBarView.isAttachedToWindow()) return; + final int winRotation = mNavigationBarView.getDisplay().getRotation(); final boolean rotateSuggestionsDisabled = RotationButtonController .hasDisable2RotateSuggestionFlag(mDisabledFlags2); @@ -1516,7 +1531,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } /** Sets {@link AutoHideController} to the navigation bar. */ - public void setAutoHideController(AutoHideController autoHideController) { + private void setAutoHideController(AutoHideController autoHideController) { mAutoHideController = autoHideController; if (mAutoHideController != null) { mAutoHideController.setNavigationBar(mAutoHideUiElement); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index b9e9240b354a..1628c71ae005 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -395,7 +395,6 @@ public class NavigationBarController implements Callbacks, void removeNavigationBar(int displayId) { NavigationBar navBar = mNavigationBars.get(displayId); if (navBar != null) { - navBar.setAutoHideController(/* autoHideController */ null); navBar.destroyView(); mNavigationBars.remove(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 61e803312b55..70c21e43b79a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -101,10 +101,7 @@ import java.util.function.Consumer; public class NavigationBarView extends FrameLayout implements NavigationModeController.ModeChangedListener { final static boolean DEBUG = false; - final static String TAG = "StatusBar/NavBarView"; - - // slippery nav bar when everything is disabled, e.g. during setup - final static boolean SLIPPERY_WHEN_DISABLED = true; + final static String TAG = "NavBarView"; final static boolean ALTERNATE_CAR_MODE_UI = false; private final RegionSamplingHelper mRegionSamplingHelper; @@ -274,7 +271,7 @@ public class NavigationBarView extends FrameLayout implements }; private final Consumer<Boolean> mRotationButtonListener = (visible) -> { - if (visible) { + if (visible && mAutoHideController != null) { // If the button will actually become visible and the navbar is about to hide, // tell the statusbar to keep it around for longer mAutoHideController.touchAutoHide(); @@ -373,6 +370,12 @@ public class NavigationBarView extends FrameLayout implements } } + @Override + protected boolean onSetAlpha(int alpha) { + Log.e(TAG, "onSetAlpha", new Throwable()); + return super.onSetAlpha(alpha); + } + public void setAutoHideController(AutoHideController autoHideController) { mAutoHideController = autoHideController; } @@ -1313,6 +1316,7 @@ public class NavigationBarView extends FrameLayout implements dumpButton(pw, "back", getBackButton()); dumpButton(pw, "home", getHomeButton()); + dumpButton(pw, "handle", getHomeHandle()); dumpButton(pw, "rcnt", getRecentsButton()); dumpButton(pw, "rota", getRotateSuggestionButton()); dumpButton(pw, "a11y", getAccessibilityButton()); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java index 649ac875b4c2..8ea9ae3c21c5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java @@ -180,7 +180,7 @@ public class RotationButtonController { TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); } - void addRotationCallback(Consumer<Integer> watcher) { + void setRotationCallback(Consumer<Integer> watcher) { mRotWatcherListener = watcher; } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt index a626681c0b01..d0aa710ecea6 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt @@ -29,6 +29,7 @@ import android.util.Log import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.android.internal.logging.UiEventLogger import com.android.systemui.appops.AppOpsController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -67,6 +68,7 @@ class PrivacyDialogController( private val privacyLogger: PrivacyLogger, private val keyguardStateController: KeyguardStateController, private val appOpsController: AppOpsController, + private val uiEventLogger: UiEventLogger, @VisibleForTesting private val dialogProvider: DialogProvider ) { @@ -81,7 +83,8 @@ class PrivacyDialogController( @Main uiExecutor: Executor, privacyLogger: PrivacyLogger, keyguardStateController: KeyguardStateController, - appOpsController: AppOpsController + appOpsController: AppOpsController, + uiEventLogger: UiEventLogger ) : this( permissionManager, packageManager, @@ -93,6 +96,7 @@ class PrivacyDialogController( privacyLogger, keyguardStateController, appOpsController, + uiEventLogger, defaultDialogProvider ) @@ -105,6 +109,7 @@ class PrivacyDialogController( private val onDialogDismissed = object : PrivacyDialog.OnDialogDismissed { override fun onDialogDismissed() { privacyLogger.logPrivacyDialogDismissed() + uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED) dialog = null } } @@ -114,6 +119,8 @@ class PrivacyDialogController( val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS) intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)) + uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS, + userId, packageName) privacyLogger.logStartSettingsActivityFromDialog(packageName, userId) if (!keyguardStateController.isUnlocked) { // If we are locked, hide the dialog so the user can unlock @@ -155,7 +162,7 @@ class PrivacyDialogController( val items = usage.mapNotNull { val type = filterType(permGroupToPrivacyType(it.permGroupName)) val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) } - userInfo?.let { ui -> + if (userInfo != null || it.isPhoneCall) { type?.let { t -> // Only try to get the app name if we actually need it val appName = if (it.isPhoneCall) { @@ -171,10 +178,14 @@ class PrivacyDialogController( it.attribution, it.lastAccess, it.isActive, - ui.isManagedProfile, + // If there's no user info, we're in a phoneCall in secondary user + userInfo?.isManagedProfile ?: false, it.isPhoneCall ) } + } else { + // No matching user or phone call + null } } uiExecutor.execute { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt new file mode 100644 index 000000000000..3ecc5a5e5b00 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.privacy + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.") + PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904), + + @UiEvent(doc = "Privacy dialog is dismissed by user.") + PRIVACY_DIALOG_DISMISSED(905); + + override fun getId() = _id +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java deleted file mode 100644 index 38b20ee45946..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2017 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 static com.android.systemui.statusbar.phone.AutoTileManager.HOTSPOT; -import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION; -import static com.android.systemui.statusbar.phone.AutoTileManager.NIGHT; -import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER; -import static com.android.systemui.statusbar.phone.AutoTileManager.WORK; - -import android.content.Context; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings.Secure; -import android.text.TextUtils; -import android.util.ArraySet; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Prefs; -import com.android.systemui.Prefs.Key; -import com.android.systemui.util.UserAwareController; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - -import javax.inject.Inject; - -public class AutoAddTracker implements UserAwareController { - - private static final String[][] CONVERT_PREFS = { - {Key.QS_HOTSPOT_ADDED, HOTSPOT}, - {Key.QS_DATA_SAVER_ADDED, SAVER}, - {Key.QS_INVERT_COLORS_ADDED, INVERSION}, - {Key.QS_WORK_ADDED, WORK}, - {Key.QS_NIGHTDISPLAY_ADDED, NIGHT}, - }; - - private final ArraySet<String> mAutoAdded; - private final Context mContext; - private int mUserId; - - public AutoAddTracker(Context context, int userId) { - mContext = context; - mUserId = userId; - mAutoAdded = new ArraySet<>(getAdded()); - } - - /** - * Init method must be called after construction to start listening - */ - public void initialize() { - // TODO: remove migration code and shared preferences keys after P release - if (mUserId == UserHandle.USER_SYSTEM) { - for (String[] convertPref : CONVERT_PREFS) { - if (Prefs.getBoolean(mContext, convertPref[0], false)) { - setTileAdded(convertPref[1]); - Prefs.remove(mContext, convertPref[0]); - } - } - } - mContext.getContentResolver().registerContentObserver( - Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver, - UserHandle.USER_ALL); - } - - @Override - public void changeUser(UserHandle newUser) { - if (newUser.getIdentifier() == mUserId) { - return; - } - mUserId = newUser.getIdentifier(); - mAutoAdded.clear(); - mAutoAdded.addAll(getAdded()); - } - - @Override - public int getCurrentUserId() { - return mUserId; - } - - public boolean isAdded(String tile) { - return mAutoAdded.contains(tile); - } - - public void setTileAdded(String tile) { - if (mAutoAdded.add(tile)) { - saveTiles(); - } - } - - public void setTileRemoved(String tile) { - if (mAutoAdded.remove(tile)) { - saveTiles(); - } - } - - public void destroy() { - mContext.getContentResolver().unregisterContentObserver(mObserver); - } - - private void saveTiles() { - Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, - TextUtils.join(",", mAutoAdded), mUserId); - } - - private Collection<String> getAdded() { - String current = Secure.getStringForUser(mContext.getContentResolver(), - Secure.QS_AUTO_ADDED_TILES, mUserId); - if (current == null) { - return Collections.emptyList(); - } - return Arrays.asList(current.split(",")); - } - - @VisibleForTesting - protected final ContentObserver mObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - mAutoAdded.clear(); - mAutoAdded.addAll(getAdded()); - } - }; - - public static class Builder { - private final Context mContext; - private int mUserId; - - @Inject - public Builder(Context context) { - mContext = context; - } - - public Builder setUserId(int userId) { - mUserId = userId; - return this; - } - - public AutoAddTracker build() { - return new AutoAddTracker(mContext, mUserId); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt new file mode 100644 index 000000000000..7ffa9d931ff0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt @@ -0,0 +1,285 @@ +/* + * 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.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.text.TextUtils +import android.util.ArraySet +import android.util.Log +import androidx.annotation.GuardedBy +import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.UserAwareController +import com.android.systemui.util.settings.SecureSettings +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.concurrent.Executor +import javax.inject.Inject + +private const val TAG = "AutoAddTracker" + +/** + * Class to track tiles that have been auto-added + * + * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES]. + * + * It also handles restore gracefully. + */ +class AutoAddTracker @VisibleForTesting constructor( + private val secureSettings: SecureSettings, + private val broadcastDispatcher: BroadcastDispatcher, + private val qsHost: QSHost, + private val dumpManager: DumpManager, + private val mainHandler: Handler?, + private val backgroundExecutor: Executor, + private var userId: Int +) : UserAwareController, Dumpable { + + companion object { + private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) + } + + @GuardedBy("autoAdded") + private val autoAdded = ArraySet<String>() + private var restoredTiles: Set<String>? = null + + override val currentUserId: Int + get() = userId + + private val contentObserver = object : ContentObserver(mainHandler) { + override fun onChange( + selfChange: Boolean, + uris: Collection<Uri>, + flags: Int, + _userId: Int + ) { + if (_userId != userId) { + // Ignore changes outside of our user. We'll load the correct value on user change + return + } + loadTiles() + } + } + + private val restoreReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_SETTING_RESTORED) return + processRestoreIntent(intent) + } + } + + private fun processRestoreIntent(intent: Intent) { + when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) { + Settings.Secure.QS_TILES -> { + restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) + ?.split(",") + ?.toSet() + ?: run { + Log.w(TAG, "Null restored tiles for user $userId") + emptySet() + } + } + Settings.Secure.QS_AUTO_ADDED_TILES -> { + restoredTiles?.let { tiles -> + val restoredAutoAdded = intent + .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) + ?.split(",") + ?: emptyList() + val autoAddedBeforeRestore = intent + .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE) + ?.split(",") + ?: emptyList() + + val tilesToRemove = restoredAutoAdded.filter { it !in tiles } + if (tilesToRemove.isNotEmpty()) { + qsHost.removeTiles(tilesToRemove) + } + val tiles = synchronized(autoAdded) { + autoAdded.clear() + autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore) + getTilesFromListLocked() + } + saveTiles(tiles) + } ?: run { + Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " + + "${Settings.Secure.QS_TILES} for user $userId") + } + } + else -> {} // Do nothing for other Settings + } + } + + /** + * Init method must be called after construction to start listening + */ + fun initialize() { + dumpManager.registerDumpable(TAG, this) + loadTiles() + secureSettings.registerContentObserverForUser( + secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES), + contentObserver, + UserHandle.USER_ALL + ) + registerBroadcastReceiver() + } + + /** + * Unregister listeners, receivers and observers + */ + fun destroy() { + dumpManager.unregisterDumpable(TAG) + secureSettings.unregisterContentObserver(contentObserver) + unregisterBroadcastReceiver() + } + + private fun registerBroadcastReceiver() { + broadcastDispatcher.registerReceiver( + restoreReceiver, + FILTER, + backgroundExecutor, + UserHandle.of(userId) + ) + } + + private fun unregisterBroadcastReceiver() { + broadcastDispatcher.unregisterReceiver(restoreReceiver) + } + + override fun changeUser(newUser: UserHandle) { + if (newUser.identifier == userId) return + unregisterBroadcastReceiver() + userId = newUser.identifier + restoredTiles = null + loadTiles() + registerBroadcastReceiver() + } + + /** + * Returns `true` if the tile has been auto-added before + */ + fun isAdded(tile: String): Boolean { + return synchronized(autoAdded) { + tile in autoAdded + } + } + + /** + * Sets a tile as auto-added. + * + * From here on, [isAdded] will return true for that tile. + */ + fun setTileAdded(tile: String) { + val tiles = synchronized(autoAdded) { + if (autoAdded.add(tile)) { + getTilesFromListLocked() + } else { + null + } + } + tiles?.let { saveTiles(it) } + } + + /** + * Removes a tile from the list of auto-added. + * + * This allows for this tile to be auto-added again in the future. + */ + fun setTileRemoved(tile: String) { + val tiles = synchronized(autoAdded) { + if (autoAdded.remove(tile)) { + getTilesFromListLocked() + } else { + null + } + } + tiles?.let { saveTiles(it) } + } + + private fun getTilesFromListLocked(): String { + return TextUtils.join(",", autoAdded) + } + + private fun saveTiles(tiles: String) { + secureSettings.putStringForUser( + Settings.Secure.QS_AUTO_ADDED_TILES, + tiles, + /* tag */ null, + /* makeDefault */ false, + userId, + /* overrideableByRestore */ true + ) + } + + private fun loadTiles() { + synchronized(autoAdded) { + autoAdded.clear() + autoAdded.addAll(getAdded()) + } + } + + private fun getAdded(): Collection<String> { + val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId) + return current?.split(",") ?: emptySet() + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.println("Current user: $userId") + pw.println("Added tiles: $autoAdded") + } + + @SysUISingleton + class Builder @Inject constructor( + private val secureSettings: SecureSettings, + private val broadcastDispatcher: BroadcastDispatcher, + private val qsHost: QSHost, + private val dumpManager: DumpManager, + @Main private val handler: Handler, + @Background private val executor: Executor + ) { + private var userId: Int = 0 + + fun setUserId(_userId: Int): Builder { + userId = _userId + return this + } + + fun build(): AutoAddTracker { + return AutoAddTracker( + secureSettings, + broadcastDispatcher, + qsHost, + dumpManager, + handler, + executor, + userId + ) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 59e5eb8d6ac8..6f12e467291a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -23,6 +23,7 @@ import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Point; +import android.graphics.PointF; import android.util.AttributeSet; import android.view.View; import android.view.WindowInsets; @@ -289,6 +290,16 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } } + @Override + protected boolean isTransformedTouchPointInView(float x, float y, + View child, PointF outLocalPoint) { + // Prevent touches outside the clipped area from propagating to a child in that area. + if (mClippingEnabled && y + getTranslationY() > mFancyClippingTop) { + return false; + } + return super.isTransformedTouchPointInView(x, y, child, outLocalPoint); + } + private void updateClippingPath() { mFancyClippingPath.reset(); if (!mClippingEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 000fd1c4bd2e..9f585bdfaeb0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -37,6 +37,7 @@ public interface QSHost { void removeCallback(Callback callback); TileServices getTileServices(); void removeTile(String tileSpec); + void removeTiles(Collection<String> specs); void unmarkTileAsAutoAdded(String tileSpec); int indexOf(String tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 4739a3f4c7d6..08cb4a9d44b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.res.Configuration; import android.metrics.LogMaker; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dumpable; @@ -80,7 +81,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final QSHost.Callback mQSHostCallback = this::setTiles; - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = + @VisibleForTesting + protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @Override public void onConfigurationChange(Configuration newConfig) { @@ -156,6 +158,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); mHost.addCallback(mQSHostCallback); setTiles(); + mLastOrientation = getResources().getConfiguration().orientation; switchTileLayout(true); mDumpManager.registerDumpable(mView.getDumpableTag(), this); @@ -356,8 +359,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr return false; } return mUsingMediaPlayer && mMediaHost.getVisible() - && getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; + && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE; } private void logTiles() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 4a75810f86db..e60fb494e82b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -351,6 +351,17 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)); } + /** + * Remove many tiles at once. + * + * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called + * multiple times). + */ + @Override + public void removeTiles(Collection<String> specs) { + changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)); + } + @Override public void unmarkTileAsAutoAdded(String spec) { if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec); @@ -372,6 +383,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X */ public void addTile(String spec, int requestPosition) { + if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile"); changeTileSpecs(tileSpecs -> { if (tileSpecs.contains(spec)) return false; @@ -386,6 +398,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } void saveTilesToSettings(List<String> tileSpecs) { + if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile"); mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs), null /* tag */, false /* default */, mCurrentUser, true /* overrideable by restore */); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 77906abce625..84b961e7c48a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconMa import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.phone.StatusIconContainer; import com.android.systemui.statusbar.policy.Clock; +import com.android.systemui.statusbar.policy.VariableDateView; import java.util.List; @@ -62,11 +63,14 @@ public class QuickStatusBarHeader extends FrameLayout { protected QuickQSPanel mHeaderQsPanel; private View mDatePrivacyView; private View mDateView; + // DateView next to clock. Visible on QQS + private VariableDateView mClockDateView; private View mSecurityHeaderView; private View mClockIconsView; private View mContainer; private View mQSCarriers; + private ViewGroup mClockContainer; private Clock mClockView; private Space mDatePrivacySeparator; private View mClockIconsSeparator; @@ -86,7 +90,6 @@ public class QuickStatusBarHeader extends FrameLayout { private int mWaterfallTopInset; private int mCutOutPaddingLeft; private int mCutOutPaddingRight; - private float mViewAlpha = 1.0f; private float mKeyguardExpansionFraction; private int mTextColorPrimary = Color.TRANSPARENT; private int mTopViewMeasureHeight; @@ -123,18 +126,24 @@ public class QuickStatusBarHeader extends FrameLayout { mIconContainer = findViewById(R.id.statusIcons); mPrivacyChip = findViewById(R.id.privacy_chip); mDateView = findViewById(R.id.date); + mClockDateView = findViewById(R.id.date_clock); mSecurityHeaderView = findViewById(R.id.header_text_container); mClockIconsSeparator = findViewById(R.id.separator); mRightLayout = findViewById(R.id.rightLayout); mDateContainer = findViewById(R.id.date_container); mPrivacyContainer = findViewById(R.id.privacy_container); + mClockContainer = findViewById(R.id.clock_container); mClockView = findViewById(R.id.clock); mDatePrivacySeparator = findViewById(R.id.space); // Tint for the battery icons are handled in setupHost() mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon); updateResources(); + Configuration config = mContext.getResources().getConfiguration(); + setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE); + setSecurityHeaderContainerVisibility( + config.orientation == Configuration.ORIENTATION_LANDSCAPE); // Don't need to worry about tuner settings for this icon mBatteryRemainingIcon.setIgnoreTunerUpdates(true); @@ -177,7 +186,7 @@ public class QuickStatusBarHeader extends FrameLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) { mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight(); - updateAnimators(); + post(this::updateAnimators); } } @@ -186,6 +195,8 @@ public class QuickStatusBarHeader extends FrameLayout { super.onConfigurationChanged(newConfig); updateResources(); setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE); + setSecurityHeaderContainerVisibility( + newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE); } @Override @@ -206,6 +217,10 @@ public class QuickStatusBarHeader extends FrameLayout { mPrivacyContainer.setLayoutParams(lp); } + private void setSecurityHeaderContainerVisibility(boolean landscape) { + mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE); + } + private void updateBatteryMode() { if (mConfigShowBatteryEstimate && !mHasCenterCutout) { mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE); @@ -262,6 +277,25 @@ public class QuickStatusBarHeader extends FrameLayout { updateBatteryMode(); updateHeadersPadding(); updateAnimators(); + + updateClockDatePadding(); + } + + private void updateClockDatePadding() { + int startPadding = mContext.getResources() + .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding); + int endPadding = mContext.getResources() + .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding); + mClockView.setPaddingRelative( + startPadding, + mClockView.getPaddingTop(), + endPadding, + mClockView.getPaddingBottom() + ); + + MarginLayoutParams lp = (MarginLayoutParams) mClockDateView.getLayoutParams(); + lp.setMarginStart(endPadding); + mClockDateView.setLayoutParams(lp); } private void updateAnimators() { @@ -280,7 +314,8 @@ public class QuickStatusBarHeader extends FrameLayout { TouchAnimator.Builder builder = new TouchAnimator.Builder() .addFloat(mSecurityHeaderView, "alpha", 0, 1) // These views appear on expanding down - .addFloat(mClockView, "alpha", 0, 1) + .addFloat(mDateView, "alpha", 0, 0, 1) + .addFloat(mClockDateView, "alpha", 1, 0, 0) .addFloat(mQSCarriers, "alpha", 0, 1) .setListener(new TouchAnimator.ListenerAdapter() { @Override @@ -289,10 +324,14 @@ public class QuickStatusBarHeader extends FrameLayout { if (!mIsSingleCarrier) { mIconContainer.addIgnoredSlots(mRssiIgnoredSlots); } + // Make it gone so there's enough room for carrier names + mClockDateView.setVisibility(View.GONE); } @Override public void onAnimationStarted() { + mClockDateView.setVisibility(View.VISIBLE); + mClockDateView.setFreezeSwitching(true); setSeparatorVisibility(false); if (!mIsSingleCarrier) { mIconContainer.addIgnoredSlots(mRssiIgnoredSlots); @@ -302,6 +341,8 @@ public class QuickStatusBarHeader extends FrameLayout { @Override public void onAnimationAtStart() { super.onAnimationAtStart(); + mClockDateView.setFreezeSwitching(false); + mClockDateView.setVisibility(View.VISIBLE); setSeparatorVisibility(mShowClockIconsSeparator); // In QQS we never ignore RSSI. mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots); @@ -434,10 +475,11 @@ public class QuickStatusBarHeader extends FrameLayout { mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE); mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams(); + LinearLayout.LayoutParams lp = + (LinearLayout.LayoutParams) mClockContainer.getLayoutParams(); lp.width = visible ? 0 : WRAP_CONTENT; lp.weight = visible ? 1f : 0f; - mClockView.setLayoutParams(lp); + mClockContainer.setLayoutParams(lp); lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams(); lp.width = visible ? 0 : WRAP_CONTENT; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index da75c9e45c54..18d6e646b007 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusIconContainer; import com.android.systemui.statusbar.policy.Clock; +import com.android.systemui.statusbar.policy.VariableDateViewController; import com.android.systemui.util.ViewController; import java.util.List; @@ -71,6 +72,9 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private final QSExpansionPathInterpolator mQSExpansionPathInterpolator; private final FeatureFlags mFeatureFlags; + private final VariableDateViewController mVariableDateViewControllerDateView; + private final VariableDateViewController mVariableDateViewControllerClockDateView; + private boolean mListening; private boolean mMicCameraIndicatorsEnabled; private boolean mLocationIndicatorsEnabled; @@ -134,7 +138,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader SysuiColorExtractor colorExtractor, PrivacyDialogController privacyDialogController, QSExpansionPathInterpolator qsExpansionPathInterpolator, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + VariableDateViewController.Factory variableDateViewControllerFactory) { super(view); mPrivacyItemController = privacyItemController; mActivityStarter = activityStarter; @@ -154,6 +159,12 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mPrivacyChip = mView.findViewById(R.id.privacy_chip); mClockView = mView.findViewById(R.id.clock); mIconContainer = mView.findViewById(R.id.statusIcons); + mVariableDateViewControllerDateView = variableDateViewControllerFactory.create( + mView.requireViewById(R.id.date) + ); + mVariableDateViewControllerClockDateView = variableDateViewControllerFactory.create( + mView.requireViewById(R.id.date_clock) + ); mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags); mDemoModeReceiver = new ClockDemoModeReceiver(mClockView); @@ -205,6 +216,9 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots); mDemoModeController.addCallback(mDemoModeReceiver); + + mVariableDateViewControllerDateView.init(); + mVariableDateViewControllerClockDateView.init(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index 2e771d6fb669..b1cd03c4a2f2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -116,6 +116,9 @@ public class QSIconViewImpl extends QSIconView { : icon.getInvisibleDrawable(mContext) : null; int padding = icon != null ? icon.getPadding() : 0; if (d != null) { + if (d.getConstantState() != null) { + d = d.getConstantState().newDrawable(); + } d.setAutoMirrored(false); d.setLayoutDirection(getLayoutDirection()); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index 73d13700d61b..14e0f707d3b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -54,7 +54,7 @@ class AlarmTile @Inject constructor( private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null private val icon = ResourceIcon.get(R.drawable.ic_alarm) @VisibleForTesting - internal val defaultIntent = Intent(AlarmClock.ACTION_SET_ALARM) + internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS) private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm -> lastAlarmInfo = nextAlarm refreshState() 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 bbfcb865afa8..52b7b7107da2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -152,11 +152,7 @@ public class CastTile extends QSTileImpl<BooleanState> { } List<CastDevice> activeDevices = getActiveDevices(); - // We want to pop up the media route selection dialog if we either have no active devices - // (neither routes nor projection), or if we have an active route. In other cases, we assume - // that a projection is active. This is messy, but this tile never correctly handled the - // case where multiple devices were active :-/. - if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) { + if (willPopDetail()) { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { showDetail(true); }); @@ -165,6 +161,15 @@ public class CastTile extends QSTileImpl<BooleanState> { } } + // We want to pop up the media route selection dialog if we either have no active devices + // (neither routes nor projection), or if we have an active route. In other cases, we assume + // that a projection is active. This is messy, but this tile never correctly handled the + // case where multiple devices were active :-/. + private boolean willPopDetail() { + List<CastDevice> activeDevices = getActiveDevices(); + return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo); + } + private List<CastDevice> getActiveDevices() { ArrayList<CastDevice> activeDevices = new ArrayList<>(); for (CastDevice device : mController.getCastDevices()) { @@ -236,10 +241,12 @@ public class CastTile extends QSTileImpl<BooleanState> { state.contentDescription = state.contentDescription + "," + mContext.getString(R.string.accessibility_quick_settings_open_details); state.expandedAccessibilityClassName = Button.class.getName(); + state.forceExpandIcon = willPopDetail(); } else { state.state = Tile.STATE_UNAVAILABLE; String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); state.secondaryLabel = noWifi; + state.forceExpandIcon = false; } state.stateDescription = state.stateDescription + ", " + state.secondaryLabel; mDetailAdapter.updateItems(devices); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 6f016be18d5b..db1df77a5803 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -50,6 +50,7 @@ import javax.inject.Inject; /** Quick settings tile: Hotspot **/ public class HotspotTile extends QSTileImpl<BooleanState> { + private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot); private final Icon mWifi4EnabledStatic = ResourceIcon.get(R.drawable.ic_wifi_4_hotspot); private final Icon mWifi5EnabledStatic = ResourceIcon.get(R.drawable.ic_wifi_5_hotspot); @@ -106,7 +107,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { @Override public Intent getLongClickIntent() { - return new Intent(Settings.ACTION_TETHER_SETTINGS); + return new Intent(Settings.ACTION_WIFI_TETHER_SETTING); } @Override 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 7cb1421e3f0f..cc9e7485dcff 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.AlphaControlledSignalTileView; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; 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.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; @@ -66,15 +68,16 @@ import javax.inject.Inject; /** Quick settings tile: Internet **/ public class InternetTile extends QSTileImpl<SignalState> { private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS); - private static final Intent INTERNET_PANEL = - new Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY); protected final NetworkController mController; + private final AccessPointController mAccessPointController; private final DataUsageController mDataController; // The last updated tile state, 0: mobile, 1: wifi, 2: ethernet. private int mLastTileState = -1; protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback(); + private final InternetDialogFactory mInternetDialogFactory; + final Handler mHandler; @Inject public InternetTile( @@ -86,11 +89,16 @@ public class InternetTile extends QSTileImpl<SignalState> { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - NetworkController networkController + NetworkController networkController, + AccessPointController accessPointController, + InternetDialogFactory internetDialogFactory ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); + mInternetDialogFactory = internetDialogFactory; + mHandler = mainHandler; mController = networkController; + mAccessPointController = accessPointController; mDataController = mController.getMobileDataController(); mController.observe(getLifecycle(), mSignalCallback); } @@ -114,7 +122,9 @@ public class InternetTile extends QSTileImpl<SignalState> { @Override protected void handleClick(@Nullable View view) { - mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0); + mHandler.post(() -> mInternetDialogFactory.create(true, + mAccessPointController.canConfigMobileData(), + mAccessPointController.canConfigWifi())); } @Override @@ -429,7 +439,7 @@ public class InternetTile extends QSTileImpl<SignalState> { state.icon = ResourceIcon.get(cb.mWifiSignalIconId); } } else if (cb.mNoDefaultNetwork) { - if (cb.mNoNetworksAvailable) { + if (cb.mNoNetworksAvailable || !cb.mEnabled) { state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable); state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable); } else { @@ -489,7 +499,7 @@ public class InternetTile extends QSTileImpl<SignalState> { state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable); state.secondaryLabel = r.getString(R.string.status_bar_airplane); } else if (cb.mNoDefaultNetwork) { - if (cb.mNoNetworksAvailable) { + if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) { state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable); state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable); } else { 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 0bbb5bdd851a..4e936b8137af 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -16,12 +16,19 @@ package com.android.systemui.qs.tiles; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; + +import android.Manifest; +import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.os.Handler; import android.os.Looper; import android.provider.Settings; +import android.provider.Settings.Secure; import android.service.quicksettings.Tile; import android.view.View; import android.widget.Switch; @@ -38,18 +45,25 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.SecureSetting; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; +import com.android.systemui.util.settings.SecureSettings; import javax.inject.Inject; /** Quick settings tile: Rotation **/ -public class RotationLockTile extends QSTileImpl<BooleanState> { +public class RotationLockTile extends QSTileImpl<BooleanState> implements + BatteryController.BatteryStateChangeCallback { private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate); private final RotationLockController mController; + private final SensorPrivacyManager mPrivacyManager; + private final BatteryController mBatteryController; + private final SecureSetting mSetting; @Inject public RotationLockTile( @@ -61,12 +75,38 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - RotationLockController rotationLockController + RotationLockController rotationLockController, + SensorPrivacyManager privacyManager, + BatteryController batteryController, + SecureSettings secureSettings ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = rotationLockController; mController.observe(this, mCallback); + mPrivacyManager = privacyManager; + mBatteryController = batteryController; + mPrivacyManager + .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> refreshState()); + int currentUser = host.getUserContext().getUserId(); + mSetting = new SecureSetting( + secureSettings, + mHandler, + Secure.CAMERA_AUTOROTATE, + currentUser + ) { + @Override + protected void handleValueChanged(int value, boolean observedChange) { + // mHandler is the background handler so calling this is OK + handleRefreshState(value); + } + }; + mBatteryController.observe(getLifecycle(), this); + } + + @Override + public void onPowerSaveChanged(boolean isPowerSave) { + refreshState(); } @Override @@ -95,14 +135,33 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { protected void handleUpdateState(BooleanState state, Object arg) { final boolean rotationLocked = mController.isRotationLocked(); + final boolean powerSave = mBatteryController.isPowerSave(); + final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled( + SensorPrivacyManager.Sensors.CAMERA); + final boolean cameraRotation = + !powerSave && !cameraLocked && hasSufficientPermission(mContext) + && mController.isCameraRotationEnabled(); state.value = !rotationLocked; state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label); state.icon = mIcon; state.contentDescription = getAccessibilityString(rotationLocked); + if (!rotationLocked && cameraRotation) { + state.secondaryLabel = mContext.getResources().getString( + R.string.rotation_lock_camera_rotation_on); + } else { + state.secondaryLabel = ""; + } + state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } + @Override + protected void handleUserSwitch(int newUserId) { + mSetting.setUserId(newUserId); + handleRefreshState(mSetting.getValue()); + } + public static boolean isCurrentOrientationLockPortrait(RotationLockController controller, Resources resources) { int lockOrientation = controller.getRotationLockOrientation(); @@ -140,4 +199,11 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { refreshState(rotationLocked); } }; + + private boolean hasSufficientPermission(Context context) { + final PackageManager packageManager = context.getPackageManager(); + final String rotationPackage = packageManager.getRotationResolverPackageName(); + return rotationPackage != null && packageManager.checkPermission( + Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java new file mode 100644 index 000000000000..99eb5b6519bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -0,0 +1,204 @@ +/* + * 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.tiles.dialog; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.text.Html; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.Utils; +import com.android.settingslib.wifi.WifiUtils; +import com.android.systemui.R; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Adapter for showing Wi-Fi networks. + */ +public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> { + + private static final String TAG = "InternetAdapter"; + private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG"; + private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; + private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"; + + private final InternetDialogController mInternetDialogController; + private List<WifiEntry> mWifiEntries; + private int mWifiEntriesCount; + + protected View mHolderView; + protected Context mContext; + + public InternetAdapter(InternetDialogController controller) { + mInternetDialogController = controller; + } + + @Override + public InternetViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, + int viewType) { + mContext = viewGroup.getContext(); + mHolderView = LayoutInflater.from(mContext).inflate(R.layout.internet_list_item, + viewGroup, false); + return new InternetViewHolder(mHolderView, mInternetDialogController); + } + + @Override + public void onBindViewHolder(@NonNull InternetViewHolder viewHolder, int position) { + if (mWifiEntries == null || position >= mWifiEntriesCount) { + return; + } + viewHolder.onBind(mWifiEntries.get(position)); + } + + /** + * Updates the Wi-Fi networks. + * + * @param wifiEntries the updated Wi-Fi entries. + * @param wifiEntriesCount the total number of Wi-Fi entries. + */ + public void setWifiEntries(@Nullable List<WifiEntry> wifiEntries, int wifiEntriesCount) { + mWifiEntries = wifiEntries; + mWifiEntriesCount = wifiEntriesCount; + } + + /** + * Gets the total number of Wi-Fi networks. + * + * @return The total number of Wi-Fi entries. + */ + @Override + public int getItemCount() { + return mWifiEntriesCount; + } + + /** + * ViewHolder for binding Wi-Fi view. + */ + static class InternetViewHolder extends RecyclerView.ViewHolder { + + final LinearLayout mContainerLayout; + final LinearLayout mWifiListLayout; + final LinearLayout mWifiNetworkLayout; + final ImageView mWifiIcon; + final TextView mWifiTitleText; + final TextView mWifiSummaryText; + final ImageView mWifiEndIcon; + final Context mContext; + final InternetDialogController mInternetDialogController; + + @VisibleForTesting + protected WifiUtils.InternetIconInjector mWifiIconInjector; + + InternetViewHolder(View view, InternetDialogController internetDialogController) { + super(view); + mContext = view.getContext(); + mInternetDialogController = internetDialogController; + mContainerLayout = view.requireViewById(R.id.internet_container); + mWifiListLayout = view.requireViewById(R.id.wifi_list); + mWifiNetworkLayout = view.requireViewById(R.id.wifi_network_layout); + mWifiIcon = view.requireViewById(R.id.wifi_icon); + mWifiTitleText = view.requireViewById(R.id.wifi_title); + mWifiSummaryText = view.requireViewById(R.id.wifi_summary); + mWifiEndIcon = view.requireViewById(R.id.wifi_end_icon); + mWifiIconInjector = mInternetDialogController.getWifiIconInjector(); + } + + void onBind(@NonNull WifiEntry wifiEntry) { + mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry)); + setWifiNetworkLayout(wifiEntry.getTitle(), + Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY)); + + final int connectedState = wifiEntry.getConnectedState(); + final int security = wifiEntry.getSecurity(); + updateEndIcon(connectedState, security); + + if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) { + mWifiListLayout.setOnClickListener( + v -> mInternetDialogController.launchWifiNetworkDetailsSetting( + wifiEntry.getKey())); + return; + } + mWifiListLayout.setOnClickListener(v -> { + if (wifiEntry.shouldEditBeforeConnect()) { + final Intent intent = new Intent(ACTION_WIFI_DIALOG); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey()); + intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false); + mContext.startActivity(intent); + } + mInternetDialogController.connect(wifiEntry); + }); + } + + void setWifiNetworkLayout(CharSequence title, CharSequence summary) { + mWifiTitleText.setText(title); + if (TextUtils.isEmpty(summary)) { + mWifiSummaryText.setVisibility(View.GONE); + return; + } + mWifiSummaryText.setVisibility(View.VISIBLE); + mWifiSummaryText.setText(summary); + } + + Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) { + if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) { + return null; + } + final Drawable drawable = mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), + wifiEntry.getLevel()); + if (drawable == null) { + return null; + } + drawable.setTint( + Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorTertiary)); + final AtomicReference<Drawable> shared = new AtomicReference<>(); + shared.set(drawable); + return shared.get(); + } + + void updateEndIcon(int connectedState, int security) { + Drawable drawable = null; + if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) { + drawable = mContext.getDrawable(R.drawable.ic_settings_24dp); + } else if (security != WifiEntry.SECURITY_NONE && security != WifiEntry.SECURITY_OWE) { + drawable = mContext.getDrawable(R.drawable.ic_friction_lock_closed); + } + if (drawable == null) { + mWifiEndIcon.setVisibility(View.GONE); + return; + } + mWifiEndIcon.setVisibility(View.VISIBLE); + mWifiEndIcon.setImageDrawable(drawable); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java new file mode 100644 index 000000000000..d0271f72153e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -0,0 +1,646 @@ +/* + * 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.tiles.dialog; + +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + +import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; + +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; +import android.text.Html; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +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.Window; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.Switch; +import android.widget.TextView; + +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks. + */ +@SysUISingleton +public class InternetDialog extends SystemUIDialog implements + InternetDialogController.InternetDialogCallback, Window.Callback { + private static final String TAG = "InternetDialog"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + static final long PROGRESS_DELAY_MS = 2000L; + + private final Handler mHandler; + private final Executor mBackgroundExecutor; + private final LinearLayoutManager mLayoutManager; + + @VisibleForTesting + protected InternetAdapter mAdapter; + @VisibleForTesting + protected WifiManager mWifiManager; + @VisibleForTesting + protected View mDialogView; + @VisibleForTesting + protected boolean mCanConfigWifi; + + private InternetDialogFactory mInternetDialogFactory; + private SubscriptionManager mSubscriptionManager; + private TelephonyManager mTelephonyManager; + private AlertDialog mAlertDialog; + private UiEventLogger mUiEventLogger; + private Context mContext; + private InternetDialogController mInternetDialogController; + private TextView mInternetDialogTitle; + private TextView mInternetDialogSubTitle; + private View mDivider; + private ProgressBar mProgressBar; + private LinearLayout mInternetDialogLayout; + private LinearLayout mConnectedWifListLayout; + private LinearLayout mMobileNetworkLayout; + private LinearLayout mTurnWifiOnLayout; + private LinearLayout mEthernetLayout; + private TextView mWifiToggleTitleText; + private LinearLayout mWifiScanNotifyLayout; + private TextView mWifiScanNotifyText; + private LinearLayout mSeeAllLayout; + private RecyclerView mWifiRecyclerView; + private ImageView mConnectedWifiIcon; + private ImageView mWifiSettingsIcon; + private TextView mConnectedWifiTitleText; + private TextView mConnectedWifiSummaryText; + private ImageView mSignalIcon; + private TextView mMobileTitleText; + private TextView mMobileSummaryText; + private Switch mMobileDataToggle; + private Switch mWiFiToggle; + private FrameLayout mDoneLayout; + private Drawable mBackgroundOn; + private int mListMaxHeight; + private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private boolean mCanConfigMobileData; + + // Wi-Fi entries + protected WifiEntry mConnectedWifiEntry; + protected int mWifiEntriesCount; + + // Wi-Fi scanning progress bar + protected boolean mIsProgressBarVisible; + protected boolean mIsSearchingHidden; + protected final Runnable mHideProgressBarRunnable = () -> { + setProgressBarVisible(false); + }; + protected Runnable mHideSearchingRunnable = () -> { + mIsSearchingHidden = true; + mInternetDialogSubTitle.setText(getSubtitleText()); + }; + + private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> { + // Set max height for list + if (mInternetDialogLayout.getHeight() > mListMaxHeight) { + ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams(); + params.height = mListMaxHeight; + mInternetDialogLayout.setLayoutParams(params); + } + }; + + public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, + InternetDialogController internetDialogController, boolean canConfigMobileData, + boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger, + @Main Handler handler, @Background Executor executor) { + super(context, R.style.Theme_SystemUI_Dialog_Internet); + if (DEBUG) { + Log.d(TAG, "Init InternetDialog"); + } + mContext = context; + mHandler = handler; + mBackgroundExecutor = executor; + mInternetDialogFactory = internetDialogFactory; + mInternetDialogController = internetDialogController; + mSubscriptionManager = mInternetDialogController.getSubscriptionManager(); + mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId(); + mTelephonyManager = mInternetDialogController.getTelephonyManager(); + mWifiManager = mInternetDialogController.getWifiManager(); + mCanConfigMobileData = canConfigMobileData; + mCanConfigWifi = canConfigWifi; + + mLayoutManager = new LinearLayoutManager(mContext) { + @Override + public boolean canScrollVertically() { + return false; + } + }; + mListMaxHeight = context.getResources().getDimensionPixelSize( + R.dimen.internet_dialog_list_max_height); + mUiEventLogger = uiEventLogger; + mAdapter = new InternetAdapter(mInternetDialogController); + if (!aboveStatusBar) { + getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (DEBUG) { + Log.d(TAG, "onCreate"); + } + mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW); + mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, + null); + final Window window = getWindow(); + final WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.gravity = Gravity.BOTTOM; + // Move down the dialog to overlay the navigation bar. + layoutParams.setFitInsetsTypes( + layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars()); + layoutParams.setFitInsetsSides(WindowInsets.Side.all()); + layoutParams.setFitInsetsIgnoringVisibility(true); + window.setAttributes(layoutParams); + window.setContentView(mDialogView); + //Only fix the width for large screen or tablet. + window.setLayout(mContext.getResources().getDimensionPixelSize( + R.dimen.internet_dialog_list_max_width), ViewGroup.LayoutParams.WRAP_CONTENT); + window.setWindowAnimations(R.style.Animation_InternetDialog); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + window.addFlags(FLAG_LAYOUT_NO_LIMITS); + + mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog); + mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title); + mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); + mDivider = mDialogView.requireViewById(R.id.divider); + mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress); + mEthernetLayout = mDialogView.requireViewById(R.id.ethernet_layout); + mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout); + mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout); + mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title); + mWifiScanNotifyLayout = mDialogView.requireViewById(R.id.wifi_scan_notify_layout); + mWifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text); + mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout); + mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon); + mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title); + mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary); + mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon); + mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout); + mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout); + mDoneLayout = mDialogView.requireViewById(R.id.done_layout); + mSignalIcon = mDialogView.requireViewById(R.id.signal_icon); + mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title); + mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary); + mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); + mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); + mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); + mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener( + mInternetListLayoutListener); + mInternetDialogTitle.setText(getDialogTitleText()); + mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); + + setOnClickListener(); + mTurnWifiOnLayout.setBackground(null); + mWifiRecyclerView.setLayoutManager(mLayoutManager); + mWifiRecyclerView.setAdapter(mAdapter); + } + + @Override + public void onStart() { + super.onStart(); + if (DEBUG) { + Log.d(TAG, "onStart"); + } + mInternetDialogController.onStart(this, mCanConfigWifi); + if (!mCanConfigWifi) { + hideWifiViews(); + } + } + + @VisibleForTesting + void hideWifiViews() { + setProgressBarVisible(false); + mTurnWifiOnLayout.setVisibility(View.GONE); + mConnectedWifListLayout.setVisibility(View.GONE); + mWifiRecyclerView.setVisibility(View.GONE); + mSeeAllLayout.setVisibility(View.GONE); + } + + @Override + public void onStop() { + super.onStop(); + if (DEBUG) { + Log.d(TAG, "onStop"); + } + mHandler.removeCallbacks(mHideProgressBarRunnable); + mHandler.removeCallbacks(mHideSearchingRunnable); + mMobileNetworkLayout.setOnClickListener(null); + mMobileDataToggle.setOnCheckedChangeListener(null); + mConnectedWifListLayout.setOnClickListener(null); + mSeeAllLayout.setOnClickListener(null); + mWiFiToggle.setOnCheckedChangeListener(null); + mDoneLayout.setOnClickListener(null); + mInternetDialogController.onStop(); + mInternetDialogFactory.destroyDialog(); + } + + @Override + public void dismissDialog() { + if (DEBUG) { + Log.d(TAG, "dismissDialog"); + } + mInternetDialogFactory.destroyDialog(); + dismiss(); + } + + /** + * Update the internet dialog when receiving the callback. + * + * @param shouldUpdateMobileNetwork {@code true} for update the mobile network layout, + * otherwise {@code false}. + */ + void updateDialog(boolean shouldUpdateMobileNetwork) { + if (DEBUG) { + Log.d(TAG, "updateDialog"); + } + if (mInternetDialogController.isAirplaneModeEnabled()) { + mInternetDialogSubTitle.setVisibility(View.GONE); + } else { + mInternetDialogSubTitle.setText(getSubtitleText()); + } + updateEthernet(); + if (shouldUpdateMobileNetwork) { + setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular() + || mInternetDialogController.isCarrierNetworkActive()); + } + + if (!mCanConfigWifi) { + return; + } + + showProgressBar(); + final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked(); + final boolean isWifiEnabled = mWifiManager.isWifiEnabled(); + final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled(); + updateWifiToggle(isWifiEnabled, isDeviceLocked); + updateConnectedWifi(isWifiEnabled, isDeviceLocked); + updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked); + + final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0) + ? View.GONE : View.VISIBLE; + mWifiRecyclerView.setVisibility(visibility); + mSeeAllLayout.setVisibility(visibility); + } + + private void setOnClickListener() { + mMobileNetworkLayout.setOnClickListener(v -> { + if (mInternetDialogController.isMobileDataEnabled() + && !mInternetDialogController.isDeviceLocked()) { + if (!mInternetDialogController.activeNetworkIsCellular()) { + mInternetDialogController.connectCarrierNetwork(); + } + } + }); + mMobileDataToggle.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + if (!isChecked && shouldShowMobileDialog()) { + showTurnOffMobileDialog(); + } else if (!shouldShowMobileDialog()) { + mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId, + isChecked, false); + } + }); + mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi()); + mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); + mWiFiToggle.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + buttonView.setChecked(isChecked); + mWifiManager.setWifiEnabled(isChecked); + }); + mDoneLayout.setOnClickListener(v -> dismiss()); + } + + @MainThread + private void updateEthernet() { + mEthernetLayout.setVisibility( + mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE); + } + + private void setMobileDataLayout(boolean isCarrierNetworkConnected) { + if (mInternetDialogController.isAirplaneModeEnabled() + || !mInternetDialogController.hasCarrier()) { + mMobileNetworkLayout.setVisibility(View.GONE); + } else { + mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled()); + mMobileNetworkLayout.setVisibility(View.VISIBLE); + mMobileTitleText.setText(getMobileNetworkTitle()); + if (!TextUtils.isEmpty(getMobileNetworkSummary())) { + mMobileSummaryText.setText( + Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY)); + mMobileSummaryText.setVisibility(View.VISIBLE); + } else { + mMobileSummaryText.setVisibility(View.GONE); + } + + mBackgroundExecutor.execute(() -> { + Drawable drawable = getSignalStrengthDrawable(); + mHandler.post(() -> { + mSignalIcon.setImageDrawable(drawable); + }); + }); + mMobileTitleText.setTextAppearance(isCarrierNetworkConnected + ? R.style.TextAppearance_InternetDialog_Active + : R.style.TextAppearance_InternetDialog); + mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected + ? R.style.TextAppearance_InternetDialog_Secondary_Active + : R.style.TextAppearance_InternetDialog_Secondary); + mMobileNetworkLayout.setBackground(isCarrierNetworkConnected ? mBackgroundOn : null); + + mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); + } + } + + @MainThread + private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) { + mWiFiToggle.setChecked(isWifiEnabled); + if (isDeviceLocked) { + mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null) + ? R.style.TextAppearance_InternetDialog_Active + : R.style.TextAppearance_InternetDialog); + } + mTurnWifiOnLayout.setBackground( + (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null); + } + + @MainThread + private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) { + if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) { + mConnectedWifListLayout.setVisibility(View.GONE); + return; + } + mConnectedWifListLayout.setVisibility(View.VISIBLE); + mConnectedWifiTitleText.setText(mConnectedWifiEntry.getTitle()); + mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false)); + mConnectedWifiIcon.setImageDrawable( + mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry)); + mWifiSettingsIcon.setColorFilter( + mContext.getColor(R.color.connected_network_primary_color)); + } + + @MainThread + private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled, + boolean isDeviceLocked) { + if (isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) { + mWifiScanNotifyLayout.setVisibility(View.GONE); + return; + } + if (TextUtils.isEmpty(mWifiScanNotifyText.getText())) { + final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo( + AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION, + v -> mInternetDialogController.launchWifiScanningSetting()); + mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify( + getContext().getText(R.string.wifi_scan_notify_message), linkInfo)); + mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance()); + } + mWifiScanNotifyLayout.setVisibility(View.VISIBLE); + } + + void onClickConnectedWifi() { + if (mConnectedWifiEntry == null) { + return; + } + mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey()); + } + + void onClickSeeMoreButton() { + mInternetDialogController.launchNetworkSetting(); + } + + CharSequence getDialogTitleText() { + return mInternetDialogController.getDialogTitleText(); + } + + CharSequence getSubtitleText() { + return mInternetDialogController.getSubtitleText( + mIsProgressBarVisible && !mIsSearchingHidden); + } + + private Drawable getSignalStrengthDrawable() { + return mInternetDialogController.getSignalStrengthDrawable(); + } + + CharSequence getMobileNetworkTitle() { + return mInternetDialogController.getMobileNetworkTitle(); + } + + String getMobileNetworkSummary() { + return mInternetDialogController.getMobileNetworkSummary(); + } + + protected void showProgressBar() { + if (mWifiManager == null || !mWifiManager.isWifiEnabled() + || mInternetDialogController.isDeviceLocked()) { + setProgressBarVisible(false); + return; + } + setProgressBarVisible(true); + if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) { + mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS); + } else if (!mIsSearchingHidden) { + mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS); + } + } + + private void setProgressBarVisible(boolean visible) { + if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null + && mAdapter.mHolderView.isAttachedToWindow()) { + mIsProgressBarVisible = true; + } + mIsProgressBarVisible = visible; + mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE); + mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE); + mInternetDialogSubTitle.setText(getSubtitleText()); + } + + private boolean shouldShowMobileDialog() { + boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, + false); + if (mInternetDialogController.isMobileDataEnabled() && !flag) { + return true; + } + return false; + } + + private void showTurnOffMobileDialog() { + CharSequence carrierName = getMobileNetworkTitle(); + boolean isInService = mInternetDialogController.isVoiceStateInService(); + if (TextUtils.isEmpty(carrierName) || !isInService) { + carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); + } + mAlertDialog = new Builder(mContext) + .setTitle(R.string.mobile_data_disable_title) + .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName)) + .setNegativeButton(android.R.string.cancel, (d, w) -> { + mMobileDataToggle.setChecked(true); + }) + .setPositiveButton( + com.android.internal.R.string.alert_windows_notification_turn_off_action, + (d, w) -> { + mInternetDialogController.setMobileDataEnabled(mContext, + mDefaultDataSubId, false, false); + mMobileDataToggle.setChecked(false); + Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true); + }) + .create(); + mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true)); + mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mAlertDialog, true); + SystemUIDialog.registerDismissListener(mAlertDialog); + SystemUIDialog.setWindowOnTop(mAlertDialog); + mAlertDialog.show(); + } + + @Override + public void onRefreshCarrierInfo() { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + public void onSimStateChanged() { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + @WorkerThread + public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + @WorkerThread + public void onLost(Network network) { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + public void onSubscriptionsChanged(int defaultDataSubId) { + mDefaultDataSubId = defaultDataSubId; + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + public void onUserMobileDataStateChanged(boolean enabled) { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + public void onServiceStateChanged(ServiceState serviceState) { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + @WorkerThread + public void onDataConnectionStateChanged(int state, int networkType) { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { + mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */)); + } + + @Override + @WorkerThread + public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, + @Nullable WifiEntry connectedEntry) { + mConnectedWifiEntry = connectedEntry; + mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); + mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount); + mHandler.post(() -> { + mAdapter.notifyDataSetChanged(); + updateDialog(false /* shouldUpdateMobileNetwork */); + }); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (mAlertDialog != null && !mAlertDialog.isShowing()) { + if (!hasFocus && isShowing()) { + dismiss(); + } + } + } + + public enum InternetDialogEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The Internet dialog became visible on the screen.") + INTERNET_DIALOG_SHOW(843); + + private final int mId; + + InternetDialogEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java new file mode 100644 index 000000000000..67e34113bebb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -0,0 +1,1100 @@ +/* + * 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.tiles.dialog; + +import static com.android.settingslib.mobile.MobileMappings.getIconKey; +import static com.android.settingslib.mobile.MobileMappings.mapIconSets; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.AccessNetworkConstants; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; + +import com.android.internal.logging.UiEventLogger; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.DeviceInfoUtils; +import com.android.settingslib.SignalIcon; +import com.android.settingslib.Utils; +import com.android.settingslib.graph.SignalDrawable; +import com.android.settingslib.mobile.MobileMappings; +import com.android.settingslib.mobile.TelephonyIcons; +import com.android.settingslib.net.SignalStrengthUtil; +import com.android.settingslib.wifi.WifiUtils; +import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastDispatcher; +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.KeyguardStateController; +import com.android.systemui.statusbar.policy.LocationController; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkController.AccessPointController; +import com.android.systemui.toast.SystemUIToast; +import com.android.systemui.toast.ToastFactory; +import com.android.systemui.util.CarrierConfigTracker; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.wifitrackerlib.MergedCarrierEntry; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; + +public class InternetDialogController implements WifiEntry.DisconnectCallback, + NetworkController.AccessPointController.AccessPointCallback { + + private static final String TAG = "InternetDialogController"; + private static final String ACTION_NETWORK_PROVIDER_SETTINGS = + "android.settings.NETWORK_PROVIDER_SETTINGS"; + private static final String ACTION_WIFI_SCANNING_SETTINGS = + "android.settings.WIFI_SCANNING_SETTINGS"; + private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; + public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); + public static final int NO_CELL_DATA_TYPE_ICON = 0; + private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off; + private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT = + R.string.tap_a_network_to_connect; + private static final int SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS = + R.string.unlock_to_view_networks; + private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS = + R.string.wifi_empty_list_wifi_on; + private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE = + R.string.non_carrier_network_unavailable; + private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE = + R.string.all_network_unavailable; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + static final int MAX_WIFI_ENTRY_COUNT = 4; + + private WifiManager mWifiManager; + private Context mContext; + private SubscriptionManager mSubscriptionManager; + private TelephonyManager mTelephonyManager; + private ConnectivityManager mConnectivityManager; + private CarrierConfigTracker mCarrierConfigTracker; + private TelephonyDisplayInfo mTelephonyDisplayInfo = + new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE); + private Handler mHandler; + private Handler mWorkerHandler; + private MobileMappings.Config mConfig = null; + private Executor mExecutor; + private AccessPointController mAccessPointController; + private IntentFilter mConnectionStateFilter; + private InternetDialogCallback mCallback; + private WifiEntry mConnectedEntry; + private int mWifiEntriesCount; + private UiEventLogger mUiEventLogger; + private BroadcastDispatcher mBroadcastDispatcher; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private GlobalSettings mGlobalSettings; + private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback; + private WindowManager mWindowManager; + private ToastFactory mToastFactory; + private SignalDrawable mSignalDrawable; + private LocationController mLocationController; + + @VisibleForTesting + static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; + @VisibleForTesting + static final float TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f; + @VisibleForTesting + static final long SHORT_DURATION_TIMEOUT = 4000; + @VisibleForTesting + protected ActivityStarter mActivityStarter; + @VisibleForTesting + protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener; + @VisibleForTesting + protected InternetTelephonyCallback mInternetTelephonyCallback; + @VisibleForTesting + protected WifiUtils.InternetIconInjector mWifiIconInjector; + @VisibleForTesting + protected boolean mCanConfigWifi; + @VisibleForTesting + protected KeyguardStateController mKeyguardStateController; + @VisibleForTesting + protected boolean mHasEthernet = false; + + private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onRefreshCarrierInfo() { + mCallback.onRefreshCarrierInfo(); + } + + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + mCallback.onSimStateChanged(); + } + }; + + protected List<SubscriptionInfo> getSubscriptionInfo() { + return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false); + } + + @Inject + public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger, + ActivityStarter starter, AccessPointController accessPointController, + SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, + @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager, + @Main Handler handler, @Main Executor mainExecutor, + BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor, + GlobalSettings globalSettings, KeyguardStateController keyguardStateController, + WindowManager windowManager, ToastFactory toastFactory, + @Background Handler workerHandler, + CarrierConfigTracker carrierConfigTracker, + LocationController locationController) { + if (DEBUG) { + Log.d(TAG, "Init InternetDialogController"); + } + mHandler = handler; + mWorkerHandler = workerHandler; + mExecutor = mainExecutor; + mContext = context; + mGlobalSettings = globalSettings; + mWifiManager = wifiManager; + mTelephonyManager = telephonyManager; + mConnectivityManager = connectivityManager; + mSubscriptionManager = subscriptionManager; + mCarrierConfigTracker = carrierConfigTracker; + mBroadcastDispatcher = broadcastDispatcher; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mKeyguardStateController = keyguardStateController; + mConnectionStateFilter = new IntentFilter(); + mConnectionStateFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); + mConnectionStateFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); + mUiEventLogger = uiEventLogger; + mActivityStarter = starter; + mAccessPointController = accessPointController; + mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext); + mConnectivityManagerNetworkCallback = new DataConnectivityListener(); + mWindowManager = windowManager; + mToastFactory = toastFactory; + mSignalDrawable = new SignalDrawable(mContext); + mLocationController = locationController; + } + + void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) { + if (DEBUG) { + Log.d(TAG, "onStart"); + } + mCallback = callback; + mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); + mAccessPointController.addAccessPointCallback(this); + mBroadcastDispatcher.registerReceiver(mConnectionStateReceiver, mConnectionStateFilter, + mExecutor); + // Listen the subscription changes + mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener(); + mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, + mOnSubscriptionsChangedListener); + mDefaultDataSubId = getDefaultDataSubscriptionId(); + if (DEBUG) { + Log.d(TAG, "Init, SubId: " + mDefaultDataSubId); + } + mConfig = MobileMappings.Config.readConfig(mContext); + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mInternetTelephonyCallback = new InternetTelephonyCallback(); + mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback); + // Listen the connectivity changes + mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback); + mCanConfigWifi = canConfigWifi; + scanWifiAccessPoints(); + } + + void onStop() { + if (DEBUG) { + Log.d(TAG, "onStop"); + } + mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver); + mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback); + mSubscriptionManager.removeOnSubscriptionsChangedListener( + mOnSubscriptionsChangedListener); + mAccessPointController.removeAccessPointCallback(this); + mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback); + mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback); + } + + @VisibleForTesting + boolean isAirplaneModeEnabled() { + return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + + @VisibleForTesting + protected int getDefaultDataSubscriptionId() { + return mSubscriptionManager.getDefaultDataSubscriptionId(); + } + + @VisibleForTesting + protected Intent getSettingsIntent() { + return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + + protected Intent getWifiDetailsSettingsIntent(String key) { + if (TextUtils.isEmpty(key)) { + if (DEBUG) { + Log.d(TAG, "connected entry's key is empty"); + } + return null; + } + return WifiUtils.getWifiDetailsSettingsIntent(key); + } + + CharSequence getDialogTitleText() { + if (isAirplaneModeEnabled()) { + return mContext.getText(R.string.airplane_mode); + } + return mContext.getText(R.string.quick_settings_internet_label); + } + + CharSequence getSubtitleText(boolean isProgressBarVisible) { + if (isAirplaneModeEnabled()) { + return null; + } + + if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) { + // When the airplane mode is off and Wi-Fi is disabled. + // Sub-Title: Wi-Fi is off + if (DEBUG) { + Log.d(TAG, "Airplane mode off + Wi-Fi off."); + } + return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF); + } + + if (isDeviceLocked()) { + // When the device is locked. + // Sub-Title: Unlock to view networks + if (DEBUG) { + Log.d(TAG, "The device is locked."); + } + return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS); + } + + if (mConnectedEntry != null || mWifiEntriesCount > 0) { + return mCanConfigWifi ? mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT) : null; + } + + if (mCanConfigWifi && isProgressBarVisible) { + // When the Wi-Fi scan result callback is received + // Sub-Title: Searching for networks... + return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS); + } + + // Sub-Title: + // show non_carrier_network_unavailable + // - while Wi-Fi on + no Wi-Fi item + // - while Wi-Fi on + no Wi-Fi item + mobile data off + // show all_network_unavailable: + // - while Wi-Fi on + no Wi-Fi item + no carrier item + // - while Wi-Fi on + no Wi-Fi item + service is out of service + // - while Wi-Fi on + no Wi-Fi item + mobile data on + no carrier data. + if (DEBUG) { + Log.d(TAG, "No Wi-Fi item."); + } + if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) { + if (DEBUG) { + Log.d(TAG, "No carrier or service is out of service."); + } + return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE); + } + + if (mCanConfigWifi && !isMobileDataEnabled()) { + if (DEBUG) { + Log.d(TAG, "Mobile data off"); + } + return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE); + } + + if (!activeNetworkIsCellular()) { + if (DEBUG) { + Log.d(TAG, "No carrier data."); + } + return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE); + } + + if (mCanConfigWifi) { + return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE); + } + return null; + } + + Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) { + if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) { + return null; + } + final Drawable drawable = + mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), wifiEntry.getLevel()); + if (drawable == null) { + return null; + } + drawable.setTint(mContext.getColor(R.color.connected_network_primary_color)); + return drawable; + } + + Drawable getSignalStrengthDrawable() { + Drawable drawable = mContext.getDrawable( + R.drawable.ic_signal_strength_zero_bar_no_internet); + try { + if (mTelephonyManager == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null"); + } + return drawable; + } + + if (isDataStateInService() || isVoiceStateInService()) { + AtomicReference<Drawable> shared = new AtomicReference<>(); + shared.set(getSignalStrengthDrawableWithLevel()); + drawable = shared.get(); + } + + int tintColor = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorTertiary); + if (activeNetworkIsCellular() || isCarrierNetworkActive()) { + tintColor = mContext.getColor(R.color.connected_network_primary_color); + } + drawable.setTint(tintColor); + } catch (Throwable e) { + e.printStackTrace(); + } + return drawable; + } + + /** + * To get the signal bar icon with level. + * + * @return The Drawable which is a signal bar icon with level. + */ + Drawable getSignalStrengthDrawableWithLevel() { + final SignalStrength strength = mTelephonyManager.getSignalStrength(); + int level = (strength == null) ? 0 : strength.getLevel(); + int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) { + level += 1; + numLevels += 1; + } + return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON, + !isMobileDataEnabled()); + } + + Drawable getSignalStrengthIcon(Context context, int level, int numLevels, + int iconType, boolean cutOut) { + mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut)); + + // Make the network type drawable + final Drawable networkDrawable = + iconType == NO_CELL_DATA_TYPE_ICON + ? EMPTY_DRAWABLE + : context.getResources().getDrawable(iconType, context.getTheme()); + + // Overlay the two drawables + final Drawable[] layers = {networkDrawable, mSignalDrawable}; + final int iconSize = + context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size); + + final LayerDrawable icons = new LayerDrawable(layers); + // Set the network type icon at the top left + icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT); + // Set the signal strength icon at the bottom right + icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT); + icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize); + icons.setTintList(Utils.getColorAttr(context, android.R.attr.textColorTertiary)); + return icons; + } + + private boolean shouldInflateSignalStrength(int subId) { + return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId); + } + + private CharSequence getUniqueSubscriptionDisplayName(int subscriptionId, Context context) { + final Map<Integer, CharSequence> displayNames = getUniqueSubscriptionDisplayNames(context); + return displayNames.getOrDefault(subscriptionId, ""); + } + + private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) { + class DisplayInfo { + public SubscriptionInfo subscriptionInfo; + public CharSequence originalName; + public CharSequence uniqueName; + } + + // Map of SubscriptionId to DisplayName + final Supplier<Stream<DisplayInfo>> originalInfos = + () -> getSubscriptionInfo() + .stream() + .filter(i -> { + // Filter out null values. + return (i != null && i.getDisplayName() != null); + }) + .map(i -> { + DisplayInfo info = new DisplayInfo(); + info.subscriptionInfo = i; + info.originalName = i.getDisplayName().toString().trim(); + return info; + }); + + // A Unique set of display names + Set<CharSequence> uniqueNames = new HashSet<>(); + // Return the set of duplicate names + final Set<CharSequence> duplicateOriginalNames = originalInfos.get() + .filter(info -> !uniqueNames.add(info.originalName)) + .map(info -> info.originalName) + .collect(Collectors.toSet()); + + // If a display name is duplicate, append the final 4 digits of the phone number. + // Creates a mapping of Subscription id to original display name + phone number display name + final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> { + if (duplicateOriginalNames.contains(info.originalName)) { + // This may return null, if the user cannot view the phone number itself. + final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context, + info.subscriptionInfo); + String lastFourDigits = ""; + if (phoneNumber != null) { + lastFourDigits = (phoneNumber.length() > 4) + ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber; + } + + if (TextUtils.isEmpty(lastFourDigits)) { + info.uniqueName = info.originalName; + } else { + info.uniqueName = info.originalName + " " + lastFourDigits; + } + + } else { + info.uniqueName = info.originalName; + } + return info; + }); + + // Check uniqueness a second time. + // We might not have had permission to view the phone numbers. + // There might also be multiple phone numbers whose last 4 digits the same. + uniqueNames.clear(); + final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get() + .filter(info -> !uniqueNames.add(info.uniqueName)) + .map(info -> info.uniqueName) + .collect(Collectors.toSet()); + + return uniqueInfos.get().map(info -> { + if (duplicatePhoneNames.contains(info.uniqueName)) { + info.uniqueName = info.originalName + " " + + info.subscriptionInfo.getSubscriptionId(); + } + return info; + }).collect(Collectors.toMap( + info -> info.subscriptionInfo.getSubscriptionId(), + info -> info.uniqueName)); + } + + CharSequence getMobileNetworkTitle() { + return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext); + } + + String getMobileNetworkSummary() { + String description = getNetworkTypeDescription(mContext, mConfig, + mTelephonyDisplayInfo, mDefaultDataSubId); + return getMobileSummary(mContext, description); + } + + /** + * Get currently description of mobile network type. + */ + private String getNetworkTypeDescription(Context context, MobileMappings.Config config, + TelephonyDisplayInfo telephonyDisplayInfo, int subId) { + String iconKey = getIconKey(telephonyDisplayInfo); + + if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) { + if (DEBUG) { + Log.d(TAG, "The description of network type is empty."); + } + return ""; + } + + int resId = mapIconSets(config).get(iconKey).dataContentDescription; + if (isCarrierNetworkActive()) { + SignalIcon.MobileIconGroup carrierMergedWifiIconGroup = + TelephonyIcons.CARRIER_MERGED_WIFI; + resId = carrierMergedWifiIconGroup.dataContentDescription; + } + + return resId != 0 + ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : ""; + } + + private String getMobileSummary(Context context, String networkTypeDescription) { + if (!isMobileDataEnabled()) { + return context.getString(R.string.mobile_data_off_summary); + } + if (!isDataStateInService()) { + return context.getString(R.string.mobile_data_no_connection); + } + String summary = networkTypeDescription; + if (activeNetworkIsCellular() || isCarrierNetworkActive()) { + summary = context.getString(R.string.preference_summary_default_combination, + context.getString(R.string.mobile_data_connection_active), + networkTypeDescription); + } + return summary; + } + + void launchNetworkSetting() { + mCallback.dismissDialog(); + mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0); + } + + void launchWifiNetworkDetailsSetting(String key) { + Intent intent = getWifiDetailsSettingsIntent(key); + if (intent != null) { + mCallback.dismissDialog(); + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + } + } + + void launchWifiScanningSetting() { + mCallback.dismissDialog(); + final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + } + + void connectCarrierNetwork() { + final MergedCarrierEntry mergedCarrierEntry = + mAccessPointController.getMergedCarrierEntry(); + if (mergedCarrierEntry != null && mergedCarrierEntry.canConnect()) { + mergedCarrierEntry.connect(null /* ConnectCallback */, false); + makeOverlayToast(R.string.wifi_wont_autoconnect_for_now); + } + } + + boolean isCarrierNetworkActive() { + final MergedCarrierEntry mergedCarrierEntry = + mAccessPointController.getMergedCarrierEntry(); + return mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork(); + } + + @WorkerThread + void setMergedCarrierWifiEnabledIfNeed(int subId, boolean enabled) { + // If the Carrier Provisions Wi-Fi Merged Networks enabled, do not set the merged carrier + // Wi-Fi state together. + if (mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(subId)) { + return; + } + + final MergedCarrierEntry entry = mAccessPointController.getMergedCarrierEntry(); + if (entry == null) { + if (DEBUG) { + Log.d(TAG, "MergedCarrierEntry is null, can not set the status."); + } + return; + } + entry.setEnabled(enabled); + } + + WifiManager getWifiManager() { + return mWifiManager; + } + + TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + SubscriptionManager getSubscriptionManager() { + return mSubscriptionManager; + } + + /** + * @return whether there is the carrier item in the slice. + */ + boolean hasCarrier() { + if (mSubscriptionManager == null) { + if (DEBUG) { + Log.d(TAG, "SubscriptionManager is null, can not check carrier."); + } + return false; + } + + if (isAirplaneModeEnabled() || mTelephonyManager == null + || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) { + return false; + } + return true; + } + + /** + * Return {@code true} if mobile data is enabled + */ + boolean isMobileDataEnabled() { + if (mTelephonyManager == null || !mTelephonyManager.isDataEnabled()) { + return false; + } + return true; + } + + /** + * Set whether to enable data for {@code subId}, also whether to disable data for other + * subscription + */ + void setMobileDataEnabled(Context context, int subId, boolean enabled, + boolean disableOtherSubscriptions) { + if (mTelephonyManager == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null, can not set mobile data."); + } + return; + } + + if (mSubscriptionManager == null) { + if (DEBUG) { + Log.d(TAG, "SubscriptionManager is null, can not set mobile data."); + } + return; + } + + mTelephonyManager.setDataEnabled(enabled); + if (disableOtherSubscriptions) { + final List<SubscriptionInfo> subInfoList = + mSubscriptionManager.getActiveSubscriptionInfoList(); + if (subInfoList != null) { + for (SubscriptionInfo subInfo : subInfoList) { + // We never disable mobile data for opportunistic subscriptions. + if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) { + context.getSystemService(TelephonyManager.class).createForSubscriptionId( + subInfo.getSubscriptionId()).setDataEnabled(false); + } + } + } + } + mWorkerHandler.post(() -> setMergedCarrierWifiEnabledIfNeed(subId, enabled)); + } + + boolean isDataStateInService() { + final ServiceState serviceState = mTelephonyManager.getServiceState(); + NetworkRegistrationInfo regInfo = + (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + return (regInfo == null) ? false : regInfo.isRegistered(); + } + + boolean isVoiceStateInService() { + if (mTelephonyManager == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null, can not detect voice state."); + } + return false; + } + + final ServiceState serviceState = mTelephonyManager.getServiceState(); + return serviceState != null + && serviceState.getState() == serviceState.STATE_IN_SERVICE; + } + + public boolean isDeviceLocked() { + return !mKeyguardStateController.isUnlocked(); + } + + boolean activeNetworkIsCellular() { + if (mConnectivityManager == null) { + if (DEBUG) { + Log.d(TAG, "ConnectivityManager is null, can not check active network."); + } + return false; + } + + final Network activeNetwork = mConnectivityManager.getActiveNetwork(); + if (activeNetwork == null) { + return false; + } + final NetworkCapabilities networkCapabilities = + mConnectivityManager.getNetworkCapabilities(activeNetwork); + if (networkCapabilities == null) { + return false; + } + return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); + } + + boolean connect(WifiEntry ap) { + if (ap == null) { + if (DEBUG) { + Log.d(TAG, "No Wi-Fi ap to connect."); + } + return false; + } + + if (ap.getWifiConfiguration() != null) { + if (DEBUG) { + Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId); + } + } else { + if (DEBUG) { + Log.d(TAG, "connect to unsaved network " + ap.getTitle()); + } + } + ap.connect(new WifiEntryConnectCallback(mActivityStarter, ap, this)); + return false; + } + + @WorkerThread + boolean isWifiScanEnabled() { + if (!mLocationController.isLocationEnabled()) { + return false; + } + return mWifiManager.isScanAlwaysAvailable(); + } + + static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback { + final ActivityStarter mActivityStarter; + final WifiEntry mWifiEntry; + final InternetDialogController mInternetDialogController; + + WifiEntryConnectCallback(ActivityStarter activityStarter, WifiEntry connectWifiEntry, + InternetDialogController internetDialogController) { + mActivityStarter = activityStarter; + mWifiEntry = connectWifiEntry; + mInternetDialogController = internetDialogController; + } + + @Override + public void onConnectResult(@ConnectStatus int status) { + if (DEBUG) { + Log.d(TAG, "onConnectResult " + status); + } + + if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) { + final Intent intent = new Intent("com.android.settings.WIFI_DIALOG") + .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mActivityStarter.startActivity(intent, false /* dismissShade */); + } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { + mInternetDialogController.makeOverlayToast(R.string.wifi_failed_connect_message); + } else { + if (DEBUG) { + Log.d(TAG, "connect failure reason=" + status); + } + } + } + } + + private void scanWifiAccessPoints() { + if (mCanConfigWifi) { + mAccessPointController.scanForAccessPoints(); + } + } + + @Override + @WorkerThread + public void onAccessPointsChanged(List<WifiEntry> accessPoints) { + if (!mCanConfigWifi) { + return; + } + + if (accessPoints == null || accessPoints.size() == 0) { + mConnectedEntry = null; + mWifiEntriesCount = 0; + if (mCallback != null) { + mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */); + } + return; + } + + boolean hasConnectedWifi = false; + final int accessPointSize = accessPoints.size(); + for (int i = 0; i < accessPointSize; i++) { + WifiEntry wifiEntry = accessPoints.get(i); + if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) { + mConnectedEntry = wifiEntry; + hasConnectedWifi = true; + break; + } + } + if (!hasConnectedWifi) { + mConnectedEntry = null; + } + + int count = MAX_WIFI_ENTRY_COUNT; + if (mHasEthernet) { + count -= 1; + } + if (hasCarrier()) { + count -= 1; + } + if (hasConnectedWifi) { + count -= 1; + } + final List<WifiEntry> wifiEntries = accessPoints.stream() + .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork() + || !wifiEntry.hasInternetAccess())) + .limit(count) + .collect(Collectors.toList()); + mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); + + if (mCallback != null) { + mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry); + } + } + + @Override + public void onSettingsActivityTriggered(Intent settingsIntent) { + } + + @Override + public void onDisconnectResult(int status) { + } + + private class InternetTelephonyCallback extends TelephonyCallback implements + TelephonyCallback.DataConnectionStateListener, + TelephonyCallback.DisplayInfoListener, + TelephonyCallback.ServiceStateListener, + TelephonyCallback.SignalStrengthsListener, + TelephonyCallback.UserMobileDataStateListener { + + @Override + public void onServiceStateChanged(@NonNull ServiceState serviceState) { + mCallback.onServiceStateChanged(serviceState); + } + + @Override + public void onDataConnectionStateChanged(int state, int networkType) { + mCallback.onDataConnectionStateChanged(state, networkType); + } + + @Override + public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) { + mCallback.onSignalStrengthsChanged(signalStrength); + } + + @Override + public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) { + mTelephonyDisplayInfo = telephonyDisplayInfo; + mCallback.onDisplayInfoChanged(telephonyDisplayInfo); + } + + @Override + public void onUserMobileDataStateChanged(boolean enabled) { + mCallback.onUserMobileDataStateChanged(enabled); + } + } + + private class InternetOnSubscriptionChangedListener + extends SubscriptionManager.OnSubscriptionsChangedListener { + InternetOnSubscriptionChangedListener() { + super(); + } + + @Override + public void onSubscriptionsChanged() { + updateListener(); + } + } + + private class DataConnectivityListener extends ConnectivityManager.NetworkCallback { + @Override + @WorkerThread + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities capabilities) { + mHasEthernet = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET); + if (mCanConfigWifi && (mHasEthernet || capabilities.hasTransport( + NetworkCapabilities.TRANSPORT_WIFI))) { + scanWifiAccessPoints(); + } + // update UI + mCallback.onCapabilitiesChanged(network, capabilities); + } + + @Override + @WorkerThread + public void onLost(@NonNull Network network) { + mHasEthernet = false; + mCallback.onLost(network); + } + } + + /** + * Return {@code true} If the Ethernet exists + */ + @MainThread + public boolean hasEthernet() { + return mHasEthernet; + } + + private final BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) { + if (DEBUG) { + Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"); + } + mConfig = MobileMappings.Config.readConfig(context); + updateListener(); + } else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) { + updateListener(); + } + } + }; + + private void updateListener() { + int defaultDataSubId = getDefaultDataSubscriptionId(); + if (mDefaultDataSubId == getDefaultDataSubscriptionId()) { + if (DEBUG) { + Log.d(TAG, "DDS: no change"); + } + return; + } + + mDefaultDataSubId = defaultDataSubId; + if (DEBUG) { + Log.d(TAG, "DDS: defaultDataSubId:" + mDefaultDataSubId); + } + if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) { + mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback); + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mTelephonyManager.registerTelephonyCallback(mHandler::post, + mInternetTelephonyCallback); + mCallback.onSubscriptionsChanged(mDefaultDataSubId); + } + } + + public WifiUtils.InternetIconInjector getWifiIconInjector() { + return mWifiIconInjector; + } + + interface InternetDialogCallback { + + void onRefreshCarrierInfo(); + + void onSimStateChanged(); + + void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities); + + void onLost(@NonNull Network network); + + void onSubscriptionsChanged(int defaultDataSubId); + + void onServiceStateChanged(ServiceState serviceState); + + void onDataConnectionStateChanged(int state, int networkType); + + void onSignalStrengthsChanged(SignalStrength signalStrength); + + void onUserMobileDataStateChanged(boolean enabled); + + void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo); + + void dismissDialog(); + + void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, + @Nullable WifiEntry connectedEntry); + } + + void makeOverlayToast(int stringId) { + final Resources res = mContext.getResources(); + + final SystemUIToast systemUIToast = mToastFactory.createToast(mContext, + res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(), + res.getConfiguration().orientation); + if (systemUIToast == null) { + return; + } + + View toastView = systemUIToast.getView(); + + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.format = PixelFormat.TRANSLUCENT; + params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; + params.y = systemUIToast.getYOffset(); + + int absGravity = Gravity.getAbsoluteGravity(systemUIToast.getGravity(), + res.getConfiguration().getLayoutDirection()); + params.gravity = absGravity; + if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { + params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT; + } + if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { + params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT; + } + + mWindowManager.addView(toastView, params); + + Animator inAnimator = systemUIToast.getInAnimation(); + if (inAnimator != null) { + inAnimator.start(); + } + + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + Animator outAnimator = systemUIToast.getOutAnimation(); + if (outAnimator != null) { + outAnimator.start(); + outAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mWindowManager.removeViewImmediate(toastView); + } + }); + } + } + }, SHORT_DURATION_TIMEOUT); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt new file mode 100644 index 000000000000..ea5df17bca58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt @@ -0,0 +1,67 @@ +/* + * 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.tiles.dialog + +import android.content.Context +import android.os.Handler +import android.util.Log +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor +import javax.inject.Inject + +private const val TAG = "InternetDialogFactory" +private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + +/** + * Factory to create [InternetDialog] objects. + */ +@SysUISingleton +class InternetDialogFactory @Inject constructor( + @Main private val handler: Handler, + @Background private val executor: Executor, + private val internetDialogController: InternetDialogController, + private val context: Context, + private val uiEventLogger: UiEventLogger +) { + companion object { + var internetDialog: InternetDialog? = null + } + + /** Creates a [InternetDialog]. */ + fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) { + if (internetDialog != null) { + if (DEBUG) { + Log.d(TAG, "InternetDialog is showing, do not create it twice.") + } + return + } else { + internetDialog = InternetDialog(context, this, internetDialogController, + canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler, + executor) + internetDialog?.show() + } + } + + fun destroyDialog() { + if (DEBUG) { + Log.d(TAG, "destroyDialog") + } + internetDialog = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java new file mode 100644 index 000000000000..6aaba997faad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java @@ -0,0 +1,14 @@ +package com.android.systemui.qs.tiles.dialog; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +public class InternetDialogUtil { + + public static boolean isProviderModelEnabled(Context context) { + if (context == null) { + return false; + } + return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 26781f4ccf09..2133cf63d1c3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -29,8 +29,8 @@ import android.content.Context; import android.graphics.Bitmap; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.media.MediaCodec; import android.media.MediaCodecInfo; -import android.media.MediaCodecList; import android.media.MediaFormat; import android.media.MediaMuxer; import android.media.MediaRecorder; @@ -187,77 +187,63 @@ public class ScreenMediaRecorder { * @param refreshRate Desired refresh rate * @return array with supported width, height, and refresh rate */ - private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate) { - double maxScale = 0; - - MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - MediaCodecInfo.VideoCapabilities maxInfo = null; - for (MediaCodecInfo codec : codecList.getCodecInfos()) { - String videoType = MediaFormat.MIMETYPE_VIDEO_AVC; - String[] types = codec.getSupportedTypes(); - for (String t : types) { - if (!t.equalsIgnoreCase(videoType)) { - continue; - } - MediaCodecInfo.CodecCapabilities capabilities = - codec.getCapabilitiesForType(videoType); - if (capabilities != null && capabilities.getVideoCapabilities() != null) { - MediaCodecInfo.VideoCapabilities vc = capabilities.getVideoCapabilities(); - - int width = vc.getSupportedWidths().getUpper(); - int height = vc.getSupportedHeights().getUpper(); - - int screenWidthAligned = screenWidth; - if (screenWidthAligned % vc.getWidthAlignment() != 0) { - screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment()); - } - int screenHeightAligned = screenHeight; - if (screenHeightAligned % vc.getHeightAlignment() != 0) { - screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment()); - } - - if (width >= screenWidthAligned && height >= screenHeightAligned - && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) { - // Desired size is supported, now get the rate - int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned, - screenHeightAligned).getUpper().intValue(); - - if (maxRate < refreshRate) { - refreshRate = maxRate; - } - Log.d(TAG, "Screen size supported at rate " + refreshRate); - return new int[]{screenWidthAligned, screenHeightAligned, refreshRate}; - } - - // Otherwise, continue searching - double scale = Math.min(((double) width / screenWidth), - ((double) height / screenHeight)); - if (scale > maxScale) { - maxScale = Math.min(1, scale); - maxInfo = vc; - } - } + private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate) + throws IOException { + String videoType = MediaFormat.MIMETYPE_VIDEO_AVC; + + // Get max size from the decoder, to ensure recordings will be playable on device + MediaCodec decoder = MediaCodec.createDecoderByType(videoType); + MediaCodecInfo.VideoCapabilities vc = decoder.getCodecInfo() + .getCapabilitiesForType(videoType).getVideoCapabilities(); + decoder.release(); + + // Check if we can support screen size as-is + int width = vc.getSupportedWidths().getUpper(); + int height = vc.getSupportedHeights().getUpper(); + + int screenWidthAligned = screenWidth; + if (screenWidthAligned % vc.getWidthAlignment() != 0) { + screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment()); + } + int screenHeightAligned = screenHeight; + if (screenHeightAligned % vc.getHeightAlignment() != 0) { + screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment()); + } + + if (width >= screenWidthAligned && height >= screenHeightAligned + && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) { + // Desired size is supported, now get the rate + int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned, + screenHeightAligned).getUpper().intValue(); + + if (maxRate < refreshRate) { + refreshRate = maxRate; } + Log.d(TAG, "Screen size supported at rate " + refreshRate); + return new int[]{screenWidthAligned, screenHeightAligned, refreshRate}; } - // Resize for max supported size - int scaledWidth = (int) (screenWidth * maxScale); - int scaledHeight = (int) (screenHeight * maxScale); - if (scaledWidth % maxInfo.getWidthAlignment() != 0) { - scaledWidth -= (scaledWidth % maxInfo.getWidthAlignment()); + // Otherwise, resize for max supported size + double scale = Math.min(((double) width / screenWidth), + ((double) height / screenHeight)); + + int scaledWidth = (int) (screenWidth * scale); + int scaledHeight = (int) (screenHeight * scale); + if (scaledWidth % vc.getWidthAlignment() != 0) { + scaledWidth -= (scaledWidth % vc.getWidthAlignment()); } - if (scaledHeight % maxInfo.getHeightAlignment() != 0) { - scaledHeight -= (scaledHeight % maxInfo.getHeightAlignment()); + if (scaledHeight % vc.getHeightAlignment() != 0) { + scaledHeight -= (scaledHeight % vc.getHeightAlignment()); } // Find max supported rate for size - int maxRate = maxInfo.getSupportedFrameRatesFor(scaledWidth, scaledHeight) + int maxRate = vc.getSupportedFrameRatesFor(scaledWidth, scaledHeight) .getUpper().intValue(); if (maxRate < refreshRate) { refreshRate = maxRate; } - Log.d(TAG, "Resized by " + maxScale + ": " + scaledWidth + ", " + scaledHeight + Log.d(TAG, "Resized by " + scale + ": " + scaledWidth + ", " + scaledHeight + ", " + refreshRate); return new int[]{scaledWidth, scaledHeight, refreshRate}; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 0eaef72ae29b..31d51f1d1a60 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -154,6 +154,7 @@ public class LongScreenshotActivity extends Activity { @Override public void onStart() { super.onStart(); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED); if (mPreview.getDrawable() != null) { // We already have an image, so no need to try to load again. @@ -245,6 +246,8 @@ public class LongScreenshotActivity extends Activity { } private void onCachedImageLoaded(ImageLoader.Result imageResult) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED); + BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap); mPreview.setImageDrawable(drawable); mPreview.setAlpha(1f); @@ -282,6 +285,8 @@ public class LongScreenshotActivity extends Activity { finish(); } if (isFinishing()) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED); + if (mScrollCaptureResponse != null) { mScrollCaptureResponse.close(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 16872b08b9c8..8def475c192c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -33,6 +33,7 @@ import static java.util.Objects.requireNonNull; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.annotation.MainThread; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -261,6 +262,7 @@ public class ScreenshotController { private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; private boolean mScreenshotTakenInPortrait; + private boolean mBlockAttach; private Animator mScreenshotAnimation; private RequestCallback mCurrentRequestCallback; @@ -559,8 +561,8 @@ public class ScreenshotController { mScreenshotView.reset(); } - mScreenshotView.updateOrientation(mWindowManager.getCurrentWindowMetrics() - .getWindowInsets().getDisplayCutout()); + mScreenshotView.updateOrientation( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); mScreenBitmap = screenshot; @@ -594,9 +596,8 @@ public class ScreenshotController { // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. mScreenshotHandler.postDelayed(this::requestScrollCapture, 150); - mScreenshotView.updateDisplayCutoutMargins( - mWindowManager.getCurrentWindowMetrics().getWindowInsets() - .getDisplayCutout()); + mScreenshotView.updateInsets( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); // screenshot animation calculations won't be valid anymore, so just end if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.end(); @@ -660,7 +661,7 @@ public class ScreenshotController { + mLastScrollCaptureResponse.getWindowTitle() + "]"); final ScrollCaptureResponse response = mLastScrollCaptureResponse; - mScreenshotView.showScrollChip(/* onClick */ () -> { + mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> { DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); Bitmap newScreenshot = captureScreenshot( @@ -732,6 +733,7 @@ public class ScreenshotController { new ViewTreeObserver.OnWindowAttachListener() { @Override public void onWindowAttached() { + mBlockAttach = false; decorView.getViewTreeObserver().removeOnWindowAttachListener(this); action.run(); } @@ -748,14 +750,16 @@ public class ScreenshotController { mWindow.setContentView(contentView); } + @MainThread private void attachWindow() { View decorView = mWindow.getDecorView(); - if (decorView.isAttachedToWindow()) { + if (decorView.isAttachedToWindow() || mBlockAttach) { return; } if (DEBUG_WINDOW) { Log.d(TAG, "attachWindow"); } + mBlockAttach = true; mWindowManager.addView(decorView, mWindowLayoutParams); decorView.requestApplyInsets(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java index 5cf018813133..169b28c6b373 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java @@ -71,7 +71,19 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "User has shared a long screenshot") SCREENSHOT_LONG_SCREENSHOT_SHARE(689), @UiEvent(doc = "User has sent a long screenshot to the editor") - SCREENSHOT_LONG_SCREENSHOT_EDIT(690); + SCREENSHOT_LONG_SCREENSHOT_EDIT(690), + @UiEvent(doc = "A long screenshot capture has started") + SCREENSHOT_LONG_SCREENSHOT_STARTED(880), + @UiEvent(doc = "The long screenshot capture failed") + SCREENSHOT_LONG_SCREENSHOT_FAILURE(881), + @UiEvent(doc = "The long screenshot capture completed successfully") + SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882), + @UiEvent(doc = "Long screenshot editor activity started") + SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED(889), + @UiEvent(doc = "Long screenshot editor activity loaded a previously saved screenshot") + SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED(890), + @UiEvent(doc = "Long screenshot editor activity finished") + SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED(891); private final int mId; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index e9e62f26a10e..dfb39e300450 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -118,8 +118,8 @@ public class ScreenshotView extends FrameLayout implements private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234; private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400; private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100; - private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350; - private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183; + private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350; + private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350; private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f; private static final float ROUNDED_CORNER_RADIUS = .25f; @@ -242,19 +242,21 @@ public class ScreenshotView extends FrameLayout implements /** * Called to display the scroll action chip when support is detected. * + * @param packageName the owning package of the window to be captured * @param onClick the action to take when the chip is clicked. */ - public void showScrollChip(Runnable onClick) { + public void showScrollChip(String packageName, Runnable onClick) { if (DEBUG_SCROLL) { Log.d(TAG, "Showing Scroll option"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName); mScrollChip.setVisibility(VISIBLE); mScrollChip.setOnClickListener((v) -> { if (DEBUG_INPUT) { Log.d(TAG, "scroll chip tapped"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0, + packageName); onClick.run(); }); } @@ -414,21 +416,30 @@ public class ScreenshotView extends FrameLayout implements mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets)); } - void updateDisplayCutoutMargins(DisplayCutout cutout) { + void updateInsets(WindowInsets insets) { int orientation = mContext.getResources().getConfiguration().orientation; mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT); FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams(); + DisplayCutout cutout = insets.getDisplayCutout(); + Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); if (cutout == null) { - p.setMargins(0, 0, 0, 0); + p.setMargins(0, 0, 0, navBarInsets.bottom); } else { Insets waterfall = cutout.getWaterfallInsets(); if (mOrientationPortrait) { - p.setMargins(waterfall.left, Math.max(cutout.getSafeInsetTop(), waterfall.top), - waterfall.right, Math.max(cutout.getSafeInsetBottom(), waterfall.bottom)); + p.setMargins( + waterfall.left, + Math.max(cutout.getSafeInsetTop(), waterfall.top), + waterfall.right, + Math.max(cutout.getSafeInsetBottom(), + Math.max(navBarInsets.bottom, waterfall.bottom))); } else { - p.setMargins(Math.max(cutout.getSafeInsetLeft(), waterfall.left), waterfall.top, - Math.max(cutout.getSafeInsetRight(), waterfall.right), waterfall.bottom); + p.setMargins( + Math.max(cutout.getSafeInsetLeft(), waterfall.left), + waterfall.top, + Math.max(cutout.getSafeInsetRight(), waterfall.right), + Math.max(navBarInsets.bottom, waterfall.bottom)); } } mStaticLeftMargin = p.leftMargin; @@ -436,10 +447,10 @@ public class ScreenshotView extends FrameLayout implements mScreenshotStatic.requestLayout(); } - void updateOrientation(DisplayCutout cutout) { + void updateOrientation(WindowInsets insets) { int orientation = mContext.getResources().getConfiguration().orientation; mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT); - updateDisplayCutoutMargins(cutout); + updateInsets(insets); int screenshotFixedSize = mContext.getResources().getDimensionPixelSize(R.dimen.global_screenshot_x_scale); ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams(); @@ -978,7 +989,6 @@ public class ScreenshotView extends FrameLayout implements mScrollingScrim.setVisibility(View.GONE); mScrollablePreview.setVisibility(View.GONE); mScreenshotStatic.setTranslationX(0); - mScreenshotPreview.setTranslationY(0); mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); mScreenshotPreview.setOnClickListener(null); @@ -994,9 +1004,6 @@ public class ScreenshotView extends FrameLayout implements mSmartChips.clear(); mQuickShareChip = null; setAlpha(1); - mDismissButton.setTranslationY(0); - mActionsContainer.setTranslationY(0); - mActionsContainerBackground.setTranslationY(0); mScreenshotSelectorView.stop(); } @@ -1024,22 +1031,19 @@ public class ScreenshotView extends FrameLayout implements setAlpha(1 - animation.getAnimatedFraction()); }); - ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1); - yAnim.setInterpolator(mAccelerateInterpolator); - yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS); - float screenshotStartY = mScreenshotPreview.getTranslationY(); - float dismissStartY = mDismissButton.getTranslationY(); - yAnim.addUpdateListener(animation -> { - float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction()); - mScreenshotPreview.setTranslationY(screenshotStartY + yDelta); - mScreenshotPreviewBorder.setTranslationY(screenshotStartY + yDelta); - mDismissButton.setTranslationY(dismissStartY + yDelta); - mActionsContainer.setTranslationY(yDelta); - mActionsContainerBackground.setTranslationY(yDelta); + ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1); + xAnim.setInterpolator(mAccelerateInterpolator); + xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS); + float deltaX = mDirectionLTR + ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth()) + : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX()); + xAnim.addUpdateListener(animation -> { + float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction()); + mScreenshotStatic.setTranslationX(currXDelta); }); AnimatorSet animSet = new AnimatorSet(); - animSet.play(yAnim).with(alphaAnim); + animSet.play(xAnim).with(alphaAnim); return animSet; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 6dc68746e3ec..ef7355a09fbf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -28,6 +28,7 @@ import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; import com.android.systemui.screenshot.ScrollCaptureClient.Session; @@ -61,6 +62,7 @@ public class ScrollCaptureController { private final Context mContext; private final Executor mBgExecutor; private final ImageTileSet mImageTileSet; + private final UiEventLogger mEventLogger; private final ScrollCaptureClient mClient; private Completer<LongScreenshot> mCaptureCompleter; @@ -69,6 +71,7 @@ public class ScrollCaptureController { private Session mSession; private ListenableFuture<CaptureResult> mTileFuture; private ListenableFuture<Void> mEndFuture; + private String mWindowOwner; static class LongScreenshot { private final ImageTileSet mImageTileSet; @@ -135,11 +138,12 @@ public class ScrollCaptureController { @Inject ScrollCaptureController(Context context, @Background Executor bgExecutor, - ScrollCaptureClient client, ImageTileSet imageTileSet) { + ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) { mContext = context; mBgExecutor = bgExecutor; mClient = client; mImageTileSet = imageTileSet; + mEventLogger = logger; } @VisibleForTesting @@ -157,6 +161,7 @@ public class ScrollCaptureController { ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) { return CallbackToFutureAdapter.getFuture(completer -> { mCaptureCompleter = completer; + mWindowOwner = response.getPackageName(); mBgExecutor.execute(() -> { float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(), SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT); @@ -173,11 +178,13 @@ public class ScrollCaptureController { if (LogConfig.DEBUG_SCROLL) { Log.d(TAG, "got session " + mSession); } + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_STARTED, 0, mWindowOwner); requestNextTile(0); } catch (InterruptedException | ExecutionException e) { // Failure to start, propagate to caller Log.e(TAG, "session start failed!"); mCaptureCompleter.setException(e); + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner); } } @@ -297,6 +304,11 @@ public class ScrollCaptureController { if (LogConfig.DEBUG_SCROLL) { Log.d(TAG, "finishCapture()"); } + if (mImageTileSet.getHeight() > 0) { + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_COMPLETED, 0, mWindowOwner); + } else { + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner); + } mEndFuture = mSession.end(); mEndFuture.addListener(() -> { if (LogConfig.DEBUG_SCROLL) { diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt new file mode 100644 index 000000000000..c50365f1bf38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt @@ -0,0 +1,74 @@ +package com.android.systemui.sensorprivacy + +import android.content.Context +import android.content.DialogInterface +import android.content.res.Resources +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.ImageView +import com.android.internal.widget.DialogTitle +import com.android.systemui.R +import com.android.systemui.statusbar.phone.SystemUIDialog + +class SensorUseDialog( + context: Context, + val sensor: Int, + val clickListener: DialogInterface.OnClickListener +) : SystemUIDialog(context) { + + // TODO move to onCreate (b/200815309) + init { + window!!.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + window!!.addSystemFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) + + val layoutInflater = LayoutInflater.from(context) + val customTitleView = layoutInflater.inflate(R.layout.sensor_use_started_title, null) + customTitleView.requireViewById<DialogTitle>(R.id.sensor_use_started_title_message) + .setText(when (sensor) { + SensorUseStartedActivity.MICROPHONE -> + R.string.sensor_privacy_start_use_mic_dialog_title + SensorUseStartedActivity.CAMERA -> + R.string.sensor_privacy_start_use_camera_dialog_title + SensorUseStartedActivity.ALL_SENSORS -> + R.string.sensor_privacy_start_use_mic_camera_dialog_title + else -> Resources.ID_NULL + }) + customTitleView.requireViewById<ImageView>(R.id.sensor_use_microphone_icon).visibility = + if (sensor == SensorUseStartedActivity.MICROPHONE || + sensor == SensorUseStartedActivity.ALL_SENSORS) { + View.VISIBLE + } else { + View.GONE + } + customTitleView.requireViewById<ImageView>(R.id.sensor_use_camera_icon).visibility = + if (sensor == SensorUseStartedActivity.CAMERA || + sensor == SensorUseStartedActivity.ALL_SENSORS) { + View.VISIBLE + } else { + View.GONE + } + + setCustomTitle(customTitleView) + setMessage(Html.fromHtml(context.getString(when (sensor) { + SensorUseStartedActivity.MICROPHONE -> + R.string.sensor_privacy_start_use_mic_dialog_content + SensorUseStartedActivity.CAMERA -> + R.string.sensor_privacy_start_use_camera_dialog_content + SensorUseStartedActivity.ALL_SENSORS -> + R.string.sensor_privacy_start_use_mic_camera_dialog_content + else -> Resources.ID_NULL + }), 0)) + + setButton(BUTTON_POSITIVE, + context.getString(com.android.internal.R.string + .sensor_privacy_start_use_dialog_turn_on_button), clickListener) + setButton(BUTTON_NEGATIVE, + context.getString(com.android.internal.R.string + .cancel), clickListener) + + setCancelable(false) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index f0fb5ebf9e1d..b0071d92481d 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -16,33 +16,28 @@ package com.android.systemui.sensorprivacy +import android.app.Activity +import android.app.AlertDialog import android.content.DialogInterface +import android.content.DialogInterface.BUTTON_NEGATIVE +import android.content.DialogInterface.BUTTON_POSITIVE import android.content.Intent import android.content.Intent.EXTRA_PACKAGE_NAME -import android.content.pm.PackageManager -import android.content.res.Resources import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS import android.hardware.SensorPrivacyManager.EXTRA_SENSOR import android.hardware.SensorPrivacyManager.Sources.DIALOG import android.os.Bundle import android.os.Handler -import android.text.Html -import android.view.View.GONE -import android.view.View.VISIBLE -import android.widget.ImageView -import com.android.internal.app.AlertActivity -import com.android.internal.widget.DialogTitle -import com.android.systemui.R +import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION +import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL +import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE +import com.android.internal.util.FrameworkStatsLog.write import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject -import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION -import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE -import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL -import com.android.internal.util.FrameworkStatsLog.write /** * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is @@ -55,7 +50,7 @@ class SensorUseStartedActivity @Inject constructor( private val keyguardStateController: KeyguardStateController, private val keyguardDismissUtil: KeyguardDismissUtil, @Background private val bgHandler: Handler -) : AlertActivity(), DialogInterface.OnClickListener { +) : Activity(), DialogInterface.OnClickListener { companion object { private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName @@ -63,9 +58,9 @@ class SensorUseStartedActivity @Inject constructor( private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L private const val UNLOCK_DELAY_MILLIS = 200L - private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA - private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE - private const val ALL_SENSORS = Integer.MAX_VALUE + internal const val CAMERA = SensorPrivacyManager.Sensors.CAMERA + internal const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE + internal const val ALL_SENSORS = Integer.MAX_VALUE } private var sensor = -1 @@ -74,6 +69,8 @@ class SensorUseStartedActivity @Inject constructor( private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback + private var mDialog: AlertDialog? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -91,7 +88,7 @@ class SensorUseStartedActivity @Inject constructor( IndividualSensorPrivacyController.Callback { _, _ -> if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && !sensorPrivacyController.isSensorBlocked(CAMERA)) { - dismiss() + finish() } } @@ -109,71 +106,22 @@ class SensorUseStartedActivity @Inject constructor( } } sensorPrivacyListener = - IndividualSensorPrivacyController.Callback { - whichSensor: Int, isBlocked: Boolean -> + IndividualSensorPrivacyController.Callback { whichSensor: Int, + isBlocked: Boolean -> if (whichSensor == sensor && !isBlocked) { - dismiss() + finish() } } sensorPrivacyController.addCallback(sensorPrivacyListener) - sensorPrivacyController.addCallback { _, isBlocked -> - if (!isBlocked) { - dismiss() - } - } - } - - mAlertParams.apply { - try { - mCustomTitleView = mInflater.inflate(R.layout.sensor_use_started_title, null) - mCustomTitleView.findViewById<DialogTitle>(R.id.sensor_use_started_title_message)!! - .setText(when (sensor) { - MICROPHONE -> - R.string.sensor_privacy_start_use_mic_dialog_title - CAMERA -> - R.string.sensor_privacy_start_use_camera_dialog_title - ALL_SENSORS -> - R.string.sensor_privacy_start_use_mic_camera_dialog_title - else -> Resources.ID_NULL - }) - - mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_microphone_icon)!! - .visibility = if (sensor == MICROPHONE || sensor == ALL_SENSORS) { - VISIBLE - } else { - GONE - } - mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_camera_icon)!! - .visibility = if (sensor == CAMERA || sensor == ALL_SENSORS) { - VISIBLE - } else { - GONE - } - - mMessage = Html.fromHtml(getString(when (sensor) { - MICROPHONE -> - R.string.sensor_privacy_start_use_mic_dialog_content - CAMERA -> - R.string.sensor_privacy_start_use_camera_dialog_content - ALL_SENSORS -> - R.string.sensor_privacy_start_use_mic_camera_dialog_content - else -> Resources.ID_NULL - }, packageManager.getApplicationInfo(sensorUsePackageName, 0) - .loadLabel(packageManager)), 0) - } catch (e: PackageManager.NameNotFoundException) { + if (!sensorPrivacyController.isSensorBlocked(sensor)) { finish() return } - - mPositiveButtonText = getString( - com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button) - mNegativeButtonText = getString(android.R.string.cancel) - mPositiveButtonListener = this@SensorUseStartedActivity - mNegativeButtonListener = this@SensorUseStartedActivity } - setupAlert() + mDialog = SensorUseDialog(this, sensor, this) + mDialog!!.show() } override fun onStart() { @@ -212,7 +160,7 @@ class SensorUseStartedActivity @Inject constructor( } } - dismiss() + finish() } override fun onStop() { @@ -229,6 +177,7 @@ class SensorUseStartedActivity @Inject constructor( override fun onDestroy() { super.onDestroy() + mDialog?.dismiss() sensorPrivacyController.removeCallback(sensorPrivacyListener) } @@ -263,4 +212,4 @@ class SensorUseStartedActivity @Inject constructor( .suppressSensorPrivacyReminders(sensor, suppressed) } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java index 8cd3632b65ba..cc5cf4b63f99 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java @@ -24,6 +24,7 @@ import android.hardware.SensorPrivacyManager; import android.os.Bundle; import android.util.Log; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -57,6 +58,8 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getWindow().addSystemFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 8e52b0da54ef..50911d162113 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -290,8 +290,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, - boolean requireConfirmation, int userId, String opPackageName, - long operationId, @BiometricMultiSensorMode int multiSensorConfig) { + boolean requireConfirmation, int userId, long operationId, String opPackageName, + long requestId, @BiometricMultiSensorMode int multiSensorConfig) { } /** @see IStatusBar#onBiometricAuthenticated() */ @@ -843,7 +843,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< @Override public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, - int userId, String opPackageName, long operationId, + int userId, long operationId, String opPackageName, long requestId, @BiometricMultiSensorMode int multiSensorConfig) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); @@ -855,6 +855,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< args.argi1 = userId; args.arg6 = opPackageName; args.arg7 = operationId; + args.arg8 = requestId; args.argi2 = multiSensorConfig; mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args) .sendToTarget(); @@ -1312,8 +1313,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< (boolean) someArgs.arg4 /* credentialAllowed */, (boolean) someArgs.arg5 /* requireConfirmation */, someArgs.argi1 /* userId */, - (String) someArgs.arg6 /* opPackageName */, (long) someArgs.arg7 /* operationId */, + (String) someArgs.arg6 /* opPackageName */, + (long) someArgs.arg8 /* requestId */, someArgs.argi2 /* multiSensorConfig */); } someArgs.recycle(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 1c933505172f..8a397199dc84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -66,6 +66,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.ViewClippingUtil; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -334,7 +335,7 @@ public class KeyguardIndicationController { info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); } } - if (info != null) { + if (!TextUtils.isEmpty(info)) { mRotateTextViewController.updateIndication( INDICATION_TYPE_OWNER_INFO, new KeyguardIndication.Builder() @@ -432,7 +433,7 @@ public class KeyguardIndicationController { } private void updateResting() { - if (mRestingIndication != null + if (!TextUtils.isEmpty(mRestingIndication) && !mRotateTextViewController.hasIndications()) { mRotateTextViewController.updateIndication( INDICATION_TYPE_RESTING, @@ -455,7 +456,8 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mContext.getResources().getString( com.android.internal.R.string.global_action_logout)) - .setTextColor(mInitialTextColorState) + .setTextColor(Utils.getColorAttr( + mContext, com.android.internal.R.attr.textColorOnAccent)) .setBackground(mContext.getDrawable( com.android.systemui.R.drawable.logout_button_background)) .setClickListener((view) -> { @@ -723,15 +725,13 @@ public class KeyguardIndicationController { } protected String computePowerIndication() { - if (mPowerCharged) { - return mContext.getResources().getString(R.string.keyguard_charged); - } - int chargingId; - String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f); if (mBatteryOverheated) { chargingId = R.string.keyguard_plugged_in_charging_limited; + String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f); return mContext.getResources().getString(chargingId, percentage); + } else if (mPowerCharged) { + return mContext.getResources().getString(R.string.keyguard_charged); } final boolean hasChargingTime = mChargingTimeRemaining > 0; @@ -759,6 +759,7 @@ public class KeyguardIndicationController { : R.string.keyguard_plugged_in_wireless; } + String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f); if (hasChargingTime) { String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes( mContext, mChargingTimeRemaining); @@ -799,7 +800,9 @@ public class KeyguardIndicationController { * Show message on the keyguard for how the user can unlock/enter their device. */ public void showActionToUnlock() { - if (mDozing) { + if (mDozing + && !mKeyguardUpdateMonitor.getUserCanSkipBouncer( + KeyguardUpdateMonitor.getCurrentUser())) { return; } @@ -810,13 +813,13 @@ public class KeyguardIndicationController { String message = mContext.getString(R.string.keyguard_retry); mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); } - } else if (mKeyguardUpdateMonitor.isScreenOn()) { + } else { showTransientIndication(mContext.getString(R.string.keyguard_unlock), false /* isError */, true /* hideOnScreenOff */); } } - private void showTryFingerprintMsg() { + private void showTryFingerprintMsg(String a11yString) { if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { // if udfps available, there will always be a tappable affordance to unlock // For example, the lock icon @@ -828,6 +831,11 @@ public class KeyguardIndicationController { } else { showTransientIndication(R.string.keyguard_try_fingerprint); } + + // Although we suppress face auth errors visually, we still announce them for a11y + if (!TextUtils.isEmpty(a11yString)) { + mLockScreenIndicationView.announceForAccessibility(a11yString); + } } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -908,7 +916,7 @@ public class KeyguardIndicationController { } else if (mKeyguardUpdateMonitor.isScreenOn()) { if (biometricSourceType == BiometricSourceType.FACE && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) { - showTryFingerprintMsg(); + showTryFingerprintMsg(helpString); return; } showTransientIndication(helpString, false /* isError */, showActionToUnlock); @@ -928,7 +936,7 @@ public class KeyguardIndicationController { && shouldSuppressFaceMsgAndShowTryFingerprintMsg() && !mStatusBarKeyguardViewManager.isBouncerShowing() && mKeyguardUpdateMonitor.isScreenOn()) { - showTryFingerprintMsg(); + showTryFingerprintMsg(errString); return; } if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { @@ -937,7 +945,7 @@ public class KeyguardIndicationController { if (!mStatusBarKeyguardViewManager.isBouncerShowing() && mKeyguardUpdateMonitor.isUdfpsEnrolled() && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { - showTryFingerprintMsg(); + showTryFingerprintMsg(errString); } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { mStatusBarKeyguardViewManager.showBouncerMessage( mContext.getResources().getString(R.string.keyguard_unlock_press), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 538db6168408..21ed9da896a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -149,7 +149,10 @@ class PowerButtonReveal( */ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - lateinit var revealAmountListener: Consumer<Float> + /** + * Listener that is called if the scrim's opaqueness changes + */ + lateinit var isScrimOpaqueChangedListener: Consumer<Boolean> /** * How much of the underlying views are revealed, in percent. 0 means they will be completely @@ -161,7 +164,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, field = value revealEffect.setRevealAmountOnScrim(value, this) - revealAmountListener.accept(value) + updateScrimOpaque() invalidate() } } @@ -201,6 +204,31 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } /** + * Is the scrim currently fully opaque + */ + var isScrimOpaque = false + private set(value) { + if (field != value) { + field = value + isScrimOpaqueChangedListener.accept(field) + } + } + + private fun updateScrimOpaque() { + isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE + } + + override fun setAlpha(alpha: Float) { + super.setAlpha(alpha) + updateScrimOpaque() + } + + override fun setVisibility(visibility: Int) { + super.setVisibility(visibility) + updateScrimOpaque() + } + + /** * Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated * via local matrix in [onDraw] so we never need to construct a new shader. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index b8334272c157..2a8771e96e7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -78,7 +78,8 @@ class NotificationShadeDepthController @Inject constructor( private var keyguardAnimator: Animator? = null private var notificationAnimator: Animator? = null private var updateScheduled: Boolean = false - private var shadeExpansion = 0f + @VisibleForTesting + var shadeExpansion = 0f private var isClosed: Boolean = true private var isOpen: Boolean = false private var isBlurred: Boolean = false @@ -92,6 +93,9 @@ class NotificationShadeDepthController @Inject constructor( // Only for dumpsys private var lastAppliedBlur = 0 + // Shade expansion offset that happens when pulling down on a HUN. + var panelPullDownMinFraction = 0f + var shadeAnimation = DepthAnimation() @VisibleForTesting @@ -181,7 +185,8 @@ class NotificationShadeDepthController @Inject constructor( if (shouldApplyShadeBlur()) shadeExpansion else 0f, false)) var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION + animationRadius * ANIMATION_BLUR_FRACTION) - val qsExpandedRatio = qsPanelExpansion * shadeExpansion + val qsExpandedRatio = Interpolators.getNotificationScrimAlpha(qsPanelExpansion, + false /* notification */) * shadeExpansion combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio)) combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress)) var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius) @@ -311,8 +316,10 @@ class NotificationShadeDepthController @Inject constructor( /** * Update blurs when pulling down the shade */ - override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) { + override fun onPanelExpansionChanged(rawExpansion: Float, tracking: Boolean) { val timestamp = SystemClock.elapsedRealtimeNanos() + val expansion = MathUtils.saturate( + (rawExpansion - panelPullDownMinFraction) / (1f - panelPullDownMinFraction)) if (shadeExpansion == expansion && prevTracking == tracking) { prevTimestamp = timestamp diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index f0d779ce1e0f..6ea79af8b9ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -182,10 +182,10 @@ public interface NotificationShadeWindowController extends RemoteInputController default void setFaceAuthDisplayBrightness(float brightness) {} /** - * How much {@link LightRevealScrim} obscures the UI. - * @param amount 0 when opaque, 1 when not transparent + * If {@link LightRevealScrim} obscures the UI. + * @param opaque if the scrim is opaque */ - default void setLightRevealScrimAmount(float amount) {} + default void setLightRevealScrimOpaque(boolean opaque) {} /** * Custom listener to pipe data back to plugins about whether or not the status bar would be diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt index 4a467ce3c987..d01fc93ee84c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt @@ -104,7 +104,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context // the active effect area. Values here should be kept in sync with the // animation implementation in the ripple shader. val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * radius * 1.5f + (1 - rippleShader.progress)) * radius * 2 canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 71546ae07ffc..0773460ecf67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -24,7 +24,6 @@ import android.app.smartspace.SmartspaceTarget import android.content.ContentResolver import android.content.Context import android.content.Intent -import android.content.pm.UserInfo import android.database.ContentObserver import android.net.Uri import android.os.Handler @@ -45,6 +44,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.concurrency.Execution import com.android.systemui.util.settings.SecureSettings import java.lang.RuntimeException @@ -67,6 +67,7 @@ class LockscreenSmartspaceController @Inject constructor( private val contentResolver: ContentResolver, private val configurationController: ConfigurationController, private val statusBarStateController: StatusBarStateController, + private val deviceProvisionedController: DeviceProvisionedController, private val execution: Execution, @Main private val uiExecutor: Executor, @Main private val handler: Handler, @@ -83,6 +84,55 @@ class LockscreenSmartspaceController @Inject constructor( private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null + private val deviceProvisionedListener = + object : DeviceProvisionedController.DeviceProvisionedListener { + override fun onDeviceProvisionedChanged() { + connectSession() + } + + override fun onUserSetupChanged() { + connectSession() + } + } + + private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> + execution.assertIsMainThread() + val filteredTargets = targets.filter(::filterSmartspaceTarget) + plugin?.onTargetsAvailable(filteredTargets) + } + + private val userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + execution.assertIsMainThread() + reloadSmartspace() + } + } + + private val settingsObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + execution.assertIsMainThread() + reloadSmartspace() + } + } + + private val configChangeListener = object : ConfigurationController.ConfigurationListener { + override fun onThemeChanged() { + execution.assertIsMainThread() + updateTextColorFromWallpaper() + } + } + + private val statusBarStateListener = object : StatusBarStateController.StateListener { + override fun onDozeAmountChanged(linear: Float, eased: Float) { + execution.assertIsMainThread() + smartspaceView.setDozeAmount(eased) + } + } + + init { + deviceProvisionedController.addCallback(deviceProvisionedListener) + } + fun isEnabled(): Boolean { execution.assertIsMainThread() @@ -141,13 +191,23 @@ class LockscreenSmartspaceController @Inject constructor( } private fun connectSession() { - if (plugin == null || session != null) { + if (plugin == null || session != null || !this::smartspaceView.isInitialized) { return } - val session = smartspaceManager.createSmartspaceSession( + + // Only connect after the device is fully provisioned to avoid connection caching + // issues + if (!deviceProvisionedController.isDeviceProvisioned() || + !deviceProvisionedController.isCurrentUserSetup()) { + return + } + + val newSession = smartspaceManager.createSmartspaceSession( SmartspaceConfig.Builder(context, "lockscreen").build()) - session.addOnTargetsAvailableListener(uiExecutor, sessionListener) + newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) + this.session = newSession + deviceProvisionedController.removeCallback(deviceProvisionedListener) userTracker.addCallback(userTrackerCallback, uiExecutor) contentResolver.registerContentObserver( secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), @@ -158,8 +218,6 @@ class LockscreenSmartspaceController @Inject constructor( configurationController.addCallback(configChangeListener) statusBarStateController.addCallback(statusBarStateListener) - this.session = session - reloadSmartspace() } @@ -198,43 +256,6 @@ class LockscreenSmartspaceController @Inject constructor( plugin?.unregisterListener(listener) } - private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> - execution.assertIsMainThread() - val filteredTargets = targets.filter(::filterSmartspaceTarget) - plugin?.onTargetsAvailable(filteredTargets) - } - - private val userTrackerCallback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - execution.assertIsMainThread() - reloadSmartspace() - } - - override fun onProfilesChanged(profiles: List<UserInfo>) { - } - } - - private val settingsObserver = object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - execution.assertIsMainThread() - reloadSmartspace() - } - } - - private val configChangeListener = object : ConfigurationController.ConfigurationListener { - override fun onThemeChanged() { - execution.assertIsMainThread() - updateTextColorFromWallpaper() - } - } - - private val statusBarStateListener = object : StatusBarStateController.StateListener { - override fun onDozeAmountChanged(linear: Float, eased: Float) { - execution.assertIsMainThread() - smartspaceView.setDozeAmount(eased) - } - } - private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { return when (t.userHandle) { userTracker.userHandle -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index dba3401cc28e..9c755e970a0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -85,23 +85,26 @@ public abstract class StackScrollerDecorView extends ExpandableView { } /** - * Set the content of this view to be visible in an animated way. - * - * @param contentVisible True if the content should be visible or false if it should be hidden. + * @param visible True if we should animate contents visible */ - public void setContentVisible(boolean contentVisible) { - setContentVisible(contentVisible, true /* animate */); + public void setContentVisible(boolean visible) { + setContentVisible(visible, true /* animate */, null /* runAfter */); } + /** - * Set the content of this view to be visible. - * @param contentVisible True if the content should be visible or false if it should be hidden. - * @param animate Should an animation be performed. + * @param visible True if the contents should be visible + * @param animate True if we should fade to new visibility + * @param runAfter Runnable to run after visibility updates */ - private void setContentVisible(boolean contentVisible, boolean animate) { - if (mContentVisible != contentVisible) { + public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) { + if (mContentVisible != visible) { mContentAnimating = animate; - mContentVisible = contentVisible; - setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable); + mContentVisible = visible; + Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> { + mContentVisibilityEndRunnable.run(); + runAfter.run(); + }; + setViewVisible(mContent, visible, animate, endRunnable); } if (!mContentAnimating) { @@ -113,6 +116,10 @@ public abstract class StackScrollerDecorView extends ExpandableView { return mContentVisible; } + public void setVisible(boolean nowVisible, boolean animate) { + setVisible(nowVisible, animate, null); + } + /** * Make this view visible. If {@code false} is passed, the view will fade out it's content * and set the view Visibility to GONE. If only the content should be changed @@ -121,7 +128,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { * @param nowVisible should the view be visible * @param animate should the change be animated. */ - public void setVisible(boolean nowVisible, boolean animate) { + public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) { if (mIsVisible != nowVisible) { mIsVisible = nowVisible; if (animate) { @@ -132,10 +139,10 @@ public abstract class StackScrollerDecorView extends ExpandableView { } else { setWillBeGone(true); } - setContentVisible(nowVisible, true /* animate */); + setContentVisible(nowVisible, true /* animate */, runAfter); } else { setVisibility(nowVisible ? VISIBLE : GONE); - setContentVisible(nowVisible, false /* animate */); + setContentVisible(nowVisible, false /* animate */, runAfter); setWillBeGone(false); notifyHeightChanged(false /* needsAnimation */); } 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 23aefd9bfd8e..594afceab63a 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 @@ -46,6 +46,7 @@ public class NotificationRoundnessManager { private Runnable mRoundingChangedCallback; private ExpandableNotificationRow mTrackedHeadsUp; private float mAppearFraction; + private boolean mIsDismissAllInProgress; private ExpandableView mSwipedView = null; private ExpandableView mViewBeforeSwipedView = null; @@ -162,6 +163,10 @@ public class NotificationRoundnessManager { } } + void setDismissAllInProgress(boolean isClearingAll) { + mIsDismissAllInProgress = isClearingAll; + } + private float getRoundness(ExpandableView view, boolean top) { if (view == null) { return 0f; @@ -171,6 +176,11 @@ public class NotificationRoundnessManager { || view == mViewAfterSwipedView) { return 1f; } + if (view instanceof ExpandableNotificationRow + && ((ExpandableNotificationRow) view).canViewBeDismissed() + && mIsDismissAllInProgress) { + return 1.0f; + } if ((view.isPinned() || (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 0660daab3720..733c0a92ec09 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 @@ -144,6 +144,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean( "persist.debug.nssl.dismiss", false /* default */); + // Delay in milli-seconds before shade closes for clear all. + private final int DELAY_BEFORE_SHADE_CLOSE = 200; + private boolean mShadeNeedsToClose = false; + private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; @@ -256,10 +260,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mExpandedInThisMotion; private boolean mShouldShowShelfOnly; protected boolean mScrollingEnabled; + private boolean mIsCurrentUserSetup; protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mDismissAllInProgress; - private boolean mFadeNotificationsOnDismiss; private FooterDismissListener mFooterDismissListener; private boolean mFlingAfterUpEvent; @@ -612,6 +616,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); mGroupMembershipManager = groupMembershipManager; mGroupExpansionManager = groupExpansionManager; + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) { @@ -683,6 +688,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mController.hasActiveClearableNotifications(ROWS_ALL); RemoteInputController remoteInputController = mRemoteInputManager.getController(); boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0) + && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && (remoteInputController == null || !remoteInputController.isRemoteInputActive()); @@ -1192,7 +1198,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void clampScrollPosition() { int scrollRange = getScrollRange(); - if (scrollRange < mOwnScrollY) { + if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) { boolean animateStackY = false; if (scrollRange < getScrollAmountToScrollBoundary() && mAnimateStackYForContentHeightChange) { @@ -1708,6 +1714,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { + if (child instanceof SectionHeaderView) { + ((StackScrollerDecorView) child).setContentVisible( + false /* visible */, true /* animate */, endRunnable); + return; + } mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration, true /* isDismissAll */); } @@ -4050,6 +4061,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable runAnimationFinishedRunnables(); clearTransient(); clearHeadsUpDisappearRunning(); + + if (mAmbientState.isDismissAllInProgress()) { + setDismissAllInProgress(false); + + if (mShadeNeedsToClose) { + mShadeNeedsToClose = false; + postDelayed( + () -> { + mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + }, + DELAY_BEFORE_SHADE_CLOSE /* delayMillis */); + } + } } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4407,6 +4431,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void setDismissAllInProgress(boolean dismissAllInProgress) { mDismissAllInProgress = dismissAllInProgress; mAmbientState.setDismissAllInProgress(dismissAllInProgress); + mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress); handleDismissAllClipping(); } @@ -4947,129 +4972,137 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mHeadsUpAppearanceController = headsUpAppearanceController; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - @VisibleForTesting - void clearNotifications(@SelectedRows int selection, boolean closeShade) { - // animate-swipe all dismissable notifications, then animate the shade closed - int numChildren = getChildCount(); + private boolean isVisible(View child) { + boolean hasClipBounds = child.getClipBounds(mTmpRect); + return child.getVisibility() == View.VISIBLE + && (!hasClipBounds || mTmpRect.height() > 0); + } - final ArrayList<View> viewsToHide = new ArrayList<>(numChildren); - final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren); - for (int i = 0; i < numChildren; i++) { - final View child = getChildAt(i); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; - boolean parentVisible = false; - boolean hasClipBounds = child.getClipBounds(mTmpRect); - if (includeChildInDismissAll(row, selection)) { - viewsToRemove.add(row); - if (child.getVisibility() == View.VISIBLE - && (!hasClipBounds || mTmpRect.height() > 0)) { - viewsToHide.add(child); - parentVisible = true; - } - } else if (child.getVisibility() == View.VISIBLE - && (!hasClipBounds || mTmpRect.height() > 0)) { - parentVisible = true; - } - List<ExpandableNotificationRow> children = row.getAttachedChildren(); - if (children != null) { - for (ExpandableNotificationRow childRow : children) { - if (includeChildInDismissAll(row, selection)) { - viewsToRemove.add(childRow); - if (parentVisible && row.areChildrenExpanded()) { - hasClipBounds = childRow.getClipBounds(mTmpRect); - if (childRow.getVisibility() == View.VISIBLE - && (!hasClipBounds || mTmpRect.height() > 0)) { - viewsToHide.add(childRow); - } - } - } - } - } + private boolean shouldHideParent(View view, @SelectedRows int selection) { + final boolean silentSectionWillBeGone = + !mController.hasNotifications(ROWS_GENTLE, false /* clearable */); + + // The only SectionHeaderView we have is the silent section header. + if (view instanceof SectionHeaderView && silentSectionWillBeGone) { + return true; + } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (isVisible(row) && includeChildInDismissAll(row, selection)) { + return true; } } + return false; + } - if (mDismissListener != null) { - mDismissListener.onDismiss(selection); - } + private boolean isChildrenVisible(ExpandableNotificationRow parent) { + List<ExpandableNotificationRow> children = parent.getAttachedChildren(); + return isVisible(parent) + && children != null + && parent.areChildrenExpanded(); + } + + // Similar to #getRowsToDismissInBackend, but filters for visible views. + private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) { + final int viewCount = getChildCount(); + final ArrayList<View> viewsToHide = new ArrayList<>(viewCount); - if (viewsToRemove.isEmpty()) { - if (closeShade && mShadeController != null) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + for (int i = 0; i < viewCount; i++) { + final View view = getChildAt(i); + + if (shouldHideParent(view, selection)) { + viewsToHide.add(view); } - return; - } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - performDismissAllAnimations( - viewsToHide, - closeShade, - () -> onDismissAllAnimationsEnd(viewsToRemove, selection)); + if (isChildrenVisible(parent)) { + for (ExpandableNotificationRow child : parent.getAttachedChildren()) { + if (isVisible(child) && includeChildInDismissAll(child, selection)) { + viewsToHide.add(child); + } + } + } + } + } + return viewsToHide; } - private boolean includeChildInDismissAll( - ExpandableNotificationRow row, + private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend( @SelectedRows int selection) { - return canChildBeDismissed(row) && matchesSelection(row, selection); + final int childCount = getChildCount(); + final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount); + + for (int i = 0; i < childCount; i++) { + final View view = getChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + continue; + } + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + if (includeChildInDismissAll(parent, selection)) { + viewsToRemove.add(parent); + } + List<ExpandableNotificationRow> children = parent.getAttachedChildren(); + if (isVisible(parent) && children != null) { + for (ExpandableNotificationRow child : children) { + if (includeChildInDismissAll(parent, selection)) { + viewsToRemove.add(child); + } + } + } + } + return viewsToRemove; } /** - * Given a list of rows, animates them away in a staggered fashion as if they were dismissed. - * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete - * handler. - * - * @param hideAnimatedList List of rows to animated away. Should only be views that are - * currently visible, or else the stagger will look funky. - * @param closeShade Whether to close the shade after the stagger animation completes. - * @param onAnimationComplete Called after the entire animation completes (including the shade - * closing if appropriate). The rows must be dismissed for real here. + * Collects a list of visible rows, and animates them away in a staggered fashion as if they + * were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd. */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void performDismissAllAnimations( - final ArrayList<View> hideAnimatedList, - final boolean closeShade, - final Runnable onAnimationComplete) { - - final Runnable onSlideAwayAnimationComplete = () -> { - if (closeShade) { - mShadeController.addPostCollapseAction(() -> { - setDismissAllInProgress(false); - onAnimationComplete.run(); - }); - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_NONE); - } else { - setDismissAllInProgress(false); - onAnimationComplete.run(); - } + @VisibleForTesting + void clearNotifications(@SelectedRows int selection, boolean closeShade) { + // Animate-swipe all dismissable notifications, then animate the shade closed + final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection); + final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend = + getRowsToDismissInBackend(selection); + if (mDismissListener != null) { + mDismissListener.onDismiss(selection); + } + final Runnable dismissInBackend = () -> { + onDismissAllAnimationsEnd(rowsToDismissInBackend, selection); }; - - if (hideAnimatedList.isEmpty()) { - onSlideAwayAnimationComplete.run(); + if (viewsToAnimateAway.isEmpty()) { + dismissInBackend.run(); return; } - - // let's disable our normal animations + // Disable normal animations setDismissAllInProgress(true); + mShadeNeedsToClose = closeShade; // Decrease the delay for every row we animate to give the sense of // accelerating the swipes - int rowDelayDecrement = 10; - int currentDelay = 140; - int totalDelay = 180; - int numItems = hideAnimatedList.size(); + final int rowDelayDecrement = 5; + int currentDelay = 60; + int totalDelay = 0; + final int numItems = viewsToAnimateAway.size(); for (int i = numItems - 1; i >= 0; i--) { - View view = hideAnimatedList.get(i); + View view = viewsToAnimateAway.get(i); Runnable endRunnable = null; if (i == 0) { - endRunnable = onSlideAwayAnimationComplete; + endRunnable = dismissInBackend; } dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE); - currentDelay = Math.max(50, currentDelay - rowDelayDecrement); + currentDelay = Math.max(30, currentDelay - rowDelayDecrement); totalDelay += currentDelay; } } + private boolean includeChildInDismissAll( + ExpandableNotificationRow row, + @SelectedRows int selection) { + return canChildBeDismissed(row) && matchesSelection(row, selection); + } + public void setNotificationActivityStarter( NotificationActivityStarter notificationActivityStarter) { mNotificationActivityStarter = notificationActivityStarter; @@ -5085,6 +5118,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mFooterDismissListener.onDismiss(); } clearNotifications(ROWS_ALL, true /* closeShade */); + footerView.setSecondaryVisible(false /* visible */, true /* animate */); }); footerView.setManageButtonClickListener(v -> { mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown()); @@ -5567,6 +5601,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** + * Sets whether the current user is set up, which is required to show the footer (b/193149550) + */ + public void setCurrentUserSetup(boolean isCurrentUserSetup) { + if (mIsCurrentUserSetup != isCurrentUserSetup) { + mIsCurrentUserSetup = isCurrentUserSetup; + updateFooter(); + } + } + + /** * A listener that is notified when the empty space below the notifications is clicked on */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 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 9e4adce47e0c..1e92ca9862f7 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 @@ -117,6 +117,8 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; @@ -144,6 +146,7 @@ public class NotificationStackScrollLayoutController { private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationRoundnessManager mNotificationRoundnessManager; private final TunerService mTunerService; + private final DeviceProvisionedController mDeviceProvisionedController; private final DynamicPrivacyController mDynamicPrivacyController; private final ConfigurationController mConfigurationController; private final ZenModeController mZenModeController; @@ -171,6 +174,7 @@ public class NotificationStackScrollLayoutController { private final NotificationLockscreenUserManager mLockscreenUserManager; // TODO: StatusBar should be encapsulated behind a Controller private final StatusBar mStatusBar; + private final NotificationGroupManagerLegacy mLegacyGroupManager; private final SectionHeaderController mSilentHeaderController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; @@ -218,6 +222,28 @@ public class NotificationStackScrollLayoutController { } }; + private final DeviceProvisionedListener mDeviceProvisionedListener = + new DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + updateCurrentUserIsSetup(); + } + + @Override + public void onUserSwitched() { + updateCurrentUserIsSetup(); + } + + @Override + public void onUserSetupChanged() { + updateCurrentUserIsSetup(); + } + + private void updateCurrentUserIsSetup() { + mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup()); + } + }; + private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { if (mView.isExpanded()) { // The bottom might change because we're using the final actual height of the view @@ -587,6 +613,7 @@ public class NotificationStackScrollLayoutController { HeadsUpManagerPhone headsUpManager, NotificationRoundnessManager notificationRoundnessManager, TunerService tunerService, + DeviceProvisionedController deviceProvisionedController, DynamicPrivacyController dynamicPrivacyController, ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, @@ -623,6 +650,7 @@ public class NotificationStackScrollLayoutController { mHeadsUpManager = headsUpManager; mNotificationRoundnessManager = notificationRoundnessManager; mTunerService = tunerService; + mDeviceProvisionedController = deviceProvisionedController; mDynamicPrivacyController = dynamicPrivacyController; mConfigurationController = configurationController; mStatusBarStateController = statusBarStateController; @@ -651,6 +679,8 @@ public class NotificationStackScrollLayoutController { mStatusBar.requestNotificationUpdate("onGroupsChanged"); } }); + mLegacyGroupManager = featureFlags.isNewNotifPipelineRenderingEnabled() + ? null : legacyGroupManager; mSilentHeaderController = silentHeaderController; mFeatureFlags = featureFlags; mNotifPipeline = notifPipeline; @@ -759,6 +789,9 @@ public class NotificationStackScrollLayoutController { return Unit.INSTANCE; }); + // callback is invoked synchronously, updating mView immediately + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + if (mView.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } @@ -1122,6 +1155,10 @@ public class NotificationStackScrollLayoutController { mZenModeController.areNotificationsHiddenInShade()); } + public boolean areNotificationsHiddenInShade() { + return mZenModeController.areNotificationsHiddenInShade(); + } + public boolean isShowingEmptyShadeView() { return mShowEmptyShadeView; } @@ -1170,6 +1207,10 @@ public class NotificationStackScrollLayoutController { * Return whether there are any clearable notifications */ public boolean hasActiveClearableNotifications(@SelectedRows int selection) { + return hasNotifications(selection, true /* clearable */); + } + + public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { if (mDynamicPrivacyController.isInLockedDownShade()) { return false; } @@ -1180,9 +1221,16 @@ public class NotificationStackScrollLayoutController { continue; } final ExpandableNotificationRow row = (ExpandableNotificationRow) child; - if (row.canViewBeDismissed() && - NotificationStackScrollLayout.matchesSelection(row, selection)) { - return true; + final boolean matchClearable = + isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed(); + final boolean inSection = + NotificationStackScrollLayout.matchesSelection(row, selection); + if (matchClearable && inSection) { + if (mLegacyGroupManager == null + || !mLegacyGroupManager.isSummaryOfSuppressedGroup( + row.getEntry().getSbn())) { + return true; + } } } return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 2c810c93b2ee..8be5de7ae56e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -366,6 +366,20 @@ public class StackScrollAlgorithm { return stackHeight / stackEndHeight; } + public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) { + for (int i = 0; i < algorithmState.visibleChildren.size(); i++) { + View child = algorithmState.visibleChildren.get(i); + if (!(child instanceof ExpandableNotificationRow)) { + continue; + } + final ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (!row.canViewBeDismissed()) { + return true; + } + } + return false; + } + // TODO(b/172289889) polish shade open from HUN /** * Populates the {@link ExpandableViewState} for a single child. @@ -430,7 +444,9 @@ public class StackScrollAlgorithm { + view.getIntrinsicHeight(); final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); ((FooterView.FooterViewState) viewState).hideContent = - isShelfShowing || noSpaceForFooter; + isShelfShowing || noSpaceForFooter + || (ambientState.isDismissAllInProgress() + && !hasOngoingNotifs(algorithmState)); } } else { if (view != ambientState.getTrackedHeadsUpRow()) { @@ -467,7 +483,6 @@ public class StackScrollAlgorithm { } } } - // Clip height of view right before shelf. viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index ee12b4b2d728..4466cfe99fe1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -46,7 +46,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_WAKEUP = 500; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; - public static final int ANIMATION_DURATION_SWIPE = 260; + public static final int ANIMATION_DURATION_SWIPE = 200; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 5a6db213d87f..e67c6ac3a7c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -291,6 +291,7 @@ public class DozeParameters implements TunerService.Tunable, @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn()); pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported()); pw.print("getPulseDuration(): "); pw.println(getPulseDuration()); pw.print("getPulseInDuration(): "); pw.println(getPulseInDuration()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java index b2cf72aca864..21c3e5e0a8d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -116,11 +116,18 @@ public class DozeScrimController implements StateListener { if (!mDozing || mPulseCallback != null) { if (DEBUG) { - Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? " + Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? " + (mPulseCallback != null)); } // Pulse suppressed. callback.onPulseFinished(); + if (!mDozing) { + mDozeLog.tracePulseDropped("device isn't dozing"); + } else { + mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback=" + + mPulseCallback); + } + return; } 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 bb48d1aba4d4..ecf3e0a953a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -206,13 +206,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ControlsListingController.ControlsListingCallback mListingCallback = new ControlsListingController.ControlsListingCallback() { public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) { - boolean available = !serviceInfos.isEmpty(); + post(() -> { + boolean available = !serviceInfos.isEmpty(); - if (available != mControlServicesAvailable) { - mControlServicesAvailable = available; - updateControlsVisibility(); - updateAffordanceColors(); - } + if (available != mControlServicesAvailable) { + mControlServicesAvailable = available; + updateControlsVisibility(); + updateAffordanceColors(); + } + }); } }; 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 f77c0520cdb1..b58cab4f1ea4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -83,8 +83,7 @@ public class KeyguardClockPositionAlgorithm { private int mNotificationStackHeight; /** - * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher - * avatar. + * Minimum top margin to avoid overlap with status bar, or multi-user switcher avatar. */ private int mMinTopMargin; @@ -150,6 +149,25 @@ public class KeyguardClockPositionAlgorithm { private boolean mIsSplitShade; /** + * Top location of the udfps icon. This includes the worst case (highest) burn-in + * offset that would make the top physically highest on the screen. + * + * Set to -1 if udfps is not enrolled on the device. + */ + private float mUdfpsTop; + + /** + * Bottom y-position of the currently visible clock + */ + private float mClockBottom; + + /** + * If true, try to keep clock aligned to the top of the display. Else, assume the clock + * is center aligned. + */ + private boolean mIsClockTopAligned; + + /** * Refreshes the dimension values. */ public void loadDimens(Resources res) { @@ -157,7 +175,7 @@ public class KeyguardClockPositionAlgorithm { R.dimen.keyguard_status_view_bottom_margin); mContainerTopPadding = - res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2; + res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); mBurnInPreventionOffsetX = res.getDimensionPixelSize( R.dimen.burn_in_prevention_offset_x); mBurnInPreventionOffsetY = res.getDimensionPixelSize( @@ -174,7 +192,8 @@ public class KeyguardClockPositionAlgorithm { int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY, boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, - float qsExpansion, int cutoutTopInset, boolean isSplitShade) { + float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop, + float clockBottom, boolean isClockTopAligned) { mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding, userSwitchHeight); mMaxShadeBottom = maxShadeBottom; @@ -193,6 +212,9 @@ public class KeyguardClockPositionAlgorithm { mQsExpansion = qsExpansion; mCutoutTopInset = cutoutTopInset; mIsSplitShade = isSplitShade; + mUdfpsTop = udfpsTop; + mClockBottom = clockBottom; + mIsClockTopAligned = isClockTopAligned; } public void run(Result result) { @@ -247,8 +269,34 @@ public class KeyguardClockPositionAlgorithm { if (clockY - mBurnInPreventionOffsetYLargeClock < mCutoutTopInset) { shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYLargeClock); } - float clockYDark = clockY + burnInPreventionOffsetY() + shift; + int burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; // requested offset + final boolean hasUdfps = mUdfpsTop > -1; + if (hasUdfps && !mIsClockTopAligned) { + // ensure clock doesn't overlap with the udfps icon + if (mUdfpsTop < mClockBottom) { + // sometimes the clock textView extends beyond udfps, so let's just use the + // space above the KeyguardStatusView/clock as our burn-in offset + burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2; + if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) { + burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; + } + shift = -burnInPreventionOffsetY; + } else { + float upperSpace = clockY - mCutoutTopInset; + float lowerSpace = mUdfpsTop - mClockBottom; + // center the burn-in offset within the upper + lower space + burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2; + if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) { + burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; + } + shift = (lowerSpace - upperSpace) / 2; + } + } + + float clockYDark = clockY + + burnInPreventionOffsetY(burnInPreventionOffsetY) + + shift; return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); } @@ -280,9 +328,7 @@ public class KeyguardClockPositionAlgorithm { return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount); } - private float burnInPreventionOffsetY() { - int offset = mBurnInPreventionOffsetYLargeClock; - + private float burnInPreventionOffsetY(int offset) { return getBurnInOffset(offset * 2, false /* xAxis */) - offset; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index f1cde8a9be7a..a5b5f1cbf1e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -28,7 +28,10 @@ import android.util.AttributeSet; import android.view.View; import android.widget.TextView; +import androidx.annotation.StyleRes; + import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.keyguard.KeyguardIndication; @@ -39,6 +42,12 @@ import java.util.LinkedList; */ public class KeyguardIndicationTextView extends TextView { private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500; + + @StyleRes + private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; + @StyleRes + private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; + private long mNextAnimationTime = 0; private boolean mAnimationsEnabled = true; private LinkedList<CharSequence> mMessages = new LinkedList<>(); @@ -136,6 +145,14 @@ public class KeyguardIndicationTextView extends TextView { public void onAnimationEnd(Animator animator) { KeyguardIndication info = mKeyguardIndicationInfo.poll(); if (info != null) { + // First, update the style. + // If a background is set on the text, we don't want shadow on the text + if (info.getBackground() != null) { + setTextAppearance(sButtonStyleId); + } else { + setTextAppearance(sStyleId); + } + setBackground(info.getBackground()); setTextColor(info.getTextColor()); setOnClickListener(info.getClickListener()); setClickable(info.getClickListener() != null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index e272d2713e2a..55e0c9b979c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -96,7 +96,8 @@ public class KeyguardStatusBarView extends RelativeLayout implements private final UserManager mUserManager; private int mSystemIconsSwitcherHiddenExpandedMargin; - private int mSystemIconsBaseMargin; + private int mStatusBarPaddingEnd; + private int mMinDotWidth; private View mSystemIconsContainer; private TintedIconManager mIconManager; private List<String> mBlockedIcons = new ArrayList<>(); @@ -157,14 +158,7 @@ public class KeyguardStatusBarView extends RelativeLayout implements mMultiUserAvatar.setLayoutParams(lp); // System icons - lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams(); - lp.setMarginStart(getResources().getDimensionPixelSize( - R.dimen.system_icons_super_container_margin_start)); - mSystemIconsContainer.setLayoutParams(lp); - mSystemIconsContainer.setPaddingRelative(mSystemIconsContainer.getPaddingStart(), - mSystemIconsContainer.getPaddingTop(), - getResources().getDimensionPixelSize(R.dimen.system_icons_keyguard_padding_end), - mSystemIconsContainer.getPaddingBottom()); + updateSystemIconsLayoutParams(); // Respect font size setting. mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, @@ -194,8 +188,10 @@ public class KeyguardStatusBarView extends RelativeLayout implements Resources res = getResources(); mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize( R.dimen.system_icons_switcher_hidden_expanded_margin); - mSystemIconsBaseMargin = res.getDimensionPixelSize( - R.dimen.system_icons_super_container_avatarless_margin_end); + mStatusBarPaddingEnd = res.getDimensionPixelSize( + R.dimen.status_bar_padding_end); + mMinDotWidth = res.getDimensionPixelSize( + R.dimen.ongoing_appops_dot_min_padding); mCutoutSideNudge = getResources().getDimensionPixelSize( R.dimen.display_cutout_margin_consumption); mShowPercentAvailable = getContext().getResources().getBoolean( @@ -243,16 +239,24 @@ public class KeyguardStatusBarView extends RelativeLayout implements private void updateSystemIconsLayoutParams() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); - // If the avatar icon is gone, we need to have some end margin to display the system icons - // correctly. - int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE - ? mSystemIconsBaseMargin - : 0; + + int marginStart = getResources().getDimensionPixelSize( + R.dimen.system_icons_super_container_margin_start); + + // Use status_bar_padding_end to replace original + // system_icons_super_container_avatarless_margin_end to prevent different end alignment + // between PhoneStatusBarView and KeyguardStatusBarView + int baseMarginEnd = mStatusBarPaddingEnd; int marginEnd = mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin : baseMarginEnd; - marginEnd = calculateMargin(marginEnd, mPadding.second); - if (marginEnd != lp.getMarginEnd()) { + + // Align PhoneStatusBar right margin/padding, only use + // 1. status bar layout: mPadding(consider round_corner + privacy dot) + // 2. icon container: R.dimen.status_bar_padding_end + + if (marginEnd != lp.getMarginEnd() || marginStart != lp.getMarginStart()) { + lp.setMarginStart(marginStart); lp.setMarginEnd(marginEnd); mSystemIconsContainer.setLayoutParams(lp); } @@ -287,7 +291,13 @@ public class KeyguardStatusBarView extends RelativeLayout implements mPadding = StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding); - setPadding(mPadding.first, waterfallTop, mPadding.second, 0); + + // consider privacy dot space + final int minLeft = isLayoutRtl() ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first; + final int minRight = isLayoutRtl() ? mPadding.second : + Math.max(mMinDotWidth, mPadding.second); + + setPadding(minLeft, waterfallTop, minRight, 0); } private boolean updateLayoutParamsNoCutout() { 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 373a276e5326..7af289fb4094 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -51,6 +51,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; @@ -626,6 +627,8 @@ public class NotificationPanelViewController extends PanelViewController { */ private float mKeyguardOnlyContentAlpha = 1.0f; + private float mUdfpsMaxYBurnInOffset; + /** * Are we currently in gesture navigation */ @@ -640,6 +643,7 @@ public class NotificationPanelViewController extends PanelViewController { private int mQsClipBottom; private boolean mQsVisible; private final ContentResolver mContentResolver; + private float mMinFraction; private final Executor mUiExecutor; private final SecureSettings mSecureSettings; @@ -859,13 +863,13 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); mBigClockContainer = mView.findViewById(R.id.big_clock_container); - UserAvatarView userAvatarView = null; + FrameLayout userAvatarContainer = null; KeyguardUserSwitcherView keyguardUserSwitcherView = null; if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) { if (mKeyguardQsUserSwitchEnabled) { ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub); - userAvatarView = (UserAvatarView) stub.inflate(); + userAvatarContainer = (FrameLayout) stub.inflate(); } else { ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub); keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate(); @@ -874,7 +878,7 @@ public class NotificationPanelViewController extends PanelViewController { updateViewControllers( mView.findViewById(R.id.keyguard_status_view), - userAvatarView, + userAvatarContainer, mKeyguardStatusBar, keyguardUserSwitcherView); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); @@ -963,10 +967,11 @@ public class NotificationPanelViewController extends PanelViewController { mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources); mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( R.dimen.notification_side_paddings); + mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); } private void updateViewControllers(KeyguardStatusView keyguardStatusView, - UserAvatarView userAvatarView, + FrameLayout userAvatarView, KeyguardStatusBarView keyguardStatusBarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { // Re-associate the KeyguardStatusViewController @@ -1118,7 +1123,7 @@ public class NotificationPanelViewController extends PanelViewController { !mKeyguardQsUserSwitchEnabled && mKeyguardUserSwitcherEnabled && isUserSwitcherEnabled; - UserAvatarView userAvatarView = (UserAvatarView) reInflateStub( + FrameLayout userAvatarView = (FrameLayout) reInflateStub( R.id.keyguard_qs_user_switch_view /* viewId */, R.id.keyguard_qs_user_switch_stub /* stubId */, R.layout.keyguard_qs_user_switch /* layoutId */, @@ -1311,7 +1316,16 @@ public class NotificationPanelViewController extends PanelViewController { float darkamount = mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() ? 1.0f : mInterpolatedDarkAmount; - mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, + + float udfpsAodTopLocation = -1f; + if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) { + FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0); + udfpsAodTopLocation = props.sensorLocationY - props.sensorRadius + - mUdfpsMaxYBurnInOffset; + } + + mClockPositionAlgorithm.setup( + mStatusBarHeaderHeightKeyguard, totalHeight - bottomPadding, mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), expandedFraction, @@ -1323,7 +1337,10 @@ public class NotificationPanelViewController extends PanelViewController { bypassEnabled, getUnlockedStackScrollerPadding(), computeQsExpansionFraction(), mDisplayTopInset, - mShouldUseSplitNotificationShade); + mShouldUseSplitNotificationShade, + udfpsAodTopLocation, + mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard), + mKeyguardStatusViewController.isClockTopAligned()); mClockPositionAlgorithm.run(mClockPositionResult); boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = animate || mAnimateNextPositionUpdate; @@ -1806,6 +1823,15 @@ public class NotificationPanelViewController extends PanelViewController { return !mQsTouchAboveFalsingThreshold; } + /** + * Percentage of panel expansion offset, caused by pulling down on a heads-up. + */ + @Override + public void setMinFraction(float minFraction) { + mMinFraction = minFraction; + mDepthController.setPanelPullDownMinFraction(mMinFraction); + } + private float computeQsExpansionFraction() { if (mQSAnimatingHiddenFromCollapsed) { // When hiding QS from collapsed state, the expansion can sometimes temporarily @@ -2320,6 +2346,12 @@ public class NotificationPanelViewController extends PanelViewController { } } top += mOverStretchAmount; + // Correction for instant expansion caused by HUN pull down/ + if (mMinFraction > 0f && mMinFraction < 1f) { + float realFraction = + (getExpandedFraction() - mMinFraction) / (1f - mMinFraction); + top *= MathUtils.saturate(realFraction / mMinFraction); + } bottom = getView().getBottom(); // notification bounds should take full screen width regardless of insets left = 0; @@ -3450,7 +3482,7 @@ public class NotificationPanelViewController extends PanelViewController { } public void setPanelScrimMinFraction(float minFraction) { - mBar.panelScrimMinFractionChanged(minFraction); + mBar.onPanelMinFractionChanged(minFraction); } public void clearNotificationEffects() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 246810a2d70b..c26782b017c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -605,12 +605,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @Override - public void setLightRevealScrimAmount(float amount) { - boolean lightRevealScrimOpaque = amount == 0; - if (mCurrentState.mLightRevealScrimOpaque == lightRevealScrimOpaque) { + public void setLightRevealScrimOpaque(boolean opaque) { + if (mCurrentState.mLightRevealScrimOpaque == opaque) { return; } - mCurrentState.mLightRevealScrimOpaque = lightRevealScrimOpaque; + mCurrentState.mLightRevealScrimOpaque = opaque; apply(mCurrentState); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 66a6e723ede2..3807b4647fe1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -325,6 +325,12 @@ public class NotificationShadeWindowViewController { // Capture all touch events in always-on. return true; } + + if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { + // capture all touches if the alt auth bouncer is showing + return true; + } + boolean intercept = false; if (mNotificationPanelViewController.isFullyExpanded() && mDragDownHelper.isDragDownEnabled() @@ -352,6 +358,12 @@ public class NotificationShadeWindowViewController { if (mStatusBarStateController.isDozing()) { handled = !mService.isPulsing(); } + + if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { + // eat the touch + handled = true; + } + if ((mDragDownHelper.isDragDownEnabled() && !handled) || mDragDownHelper.isDraggingDown()) { // we still want to finish our drag down gesture when locking the screen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index f1b6c7c3ad19..eca91a3f6fb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import static java.lang.Float.isNaN; +import android.annotation.CallSuper; import android.content.Context; import android.os.Bundle; import android.os.Parcelable; @@ -162,7 +163,13 @@ public abstract class PanelBar extends FrameLayout { return mPanel == null || mPanel.getView().dispatchTouchEvent(event); } - public abstract void panelScrimMinFractionChanged(float minFraction); + /** + * Percentage of panel expansion offset, caused by pulling down on a heads-up. + */ + @CallSuper + public void onPanelMinFractionChanged(float minFraction) { + mPanel.setMinFraction(minFraction); + } /** * @param frac the fraction from the expansion in [0, 1] 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 4de042fc4c10..30a4d69c8c24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -346,6 +346,13 @@ public abstract class PanelViewController { protected abstract float getOpeningHeight(); /** + * Minimum fraction from where expansion should start. This is set when pulling down on a + * heads-up notification. + * @param minFraction Fraction from 0 to 1. + */ + public abstract void setMinFraction(float minFraction); + + /** * @return whether the swiping direction is upwards and above a 45 degree angle compared to the * horizontal direction */ @@ -1229,10 +1236,14 @@ public abstract class PanelViewController { case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; addMovement(event); - if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) { + final boolean openShadeWithoutHun = + mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown; + if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown + || openShadeWithoutHun) { float hAbs = Math.abs(h); float touchSlop = getTouchSlop(event); - if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop)) + if ((h < -touchSlop + || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop)) && hAbs > Math.abs(x - mInitialTouchX)) { cancelHeightAnimator(); startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); @@ -1245,10 +1256,7 @@ public abstract class PanelViewController { mVelocityTracker.clear(); break; } - - // 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); + return false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index c300b11b9a34..2ca36b648b76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -269,10 +269,11 @@ public class PhoneStatusBarView extends PanelBar { } @Override - public void panelScrimMinFractionChanged(float minFraction) { + public void onPanelMinFractionChanged(float minFraction) { if (isNaN(minFraction)) { throw new IllegalArgumentException("minFraction cannot be NaN"); } + super.onPanelMinFractionChanged(minFraction); if (mMinFraction != minFraction) { mMinFraction = minFraction; updateScrimFraction(); 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 7d25aeef6f98..27c129ad34c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -587,6 +587,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (isNaN(expansionFraction)) { return; } + expansionFraction = Interpolators + .getNotificationScrimAlpha(expansionFraction, false /* notification */); boolean qsBottomVisible = qsPanelBottomY > 0; if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) { mQsExpansion = expansionFraction; @@ -1262,6 +1264,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump pw.println(mDefaultScrimAlpha); pw.print(" mExpansionFraction="); pw.println(mPanelExpansion); + pw.print(" mExpansionAffectsAlpha="); + pw.println(mExpansionAffectsAlpha); pw.print(" mState.getMaxLightRevealScrimAlpha="); pw.println(mState.getMaxLightRevealScrimAlpha()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 2c0de629de8a..15b8c67fd4ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -279,15 +279,41 @@ public enum ScrimState { BUBBLE_EXPANDED { @Override public void prepare(ScrimState previousState) { + mBehindAlpha = mClipQsScrim ? 1 : 0; + mNotifAlpha = 0; + mFrontAlpha = 0; + + mAnimationDuration = mKeyguardFadingAway + ? mKeyguardFadingAwayDuration + : StatusBar.FADE_KEYGUARD_DURATION; + + mAnimateChange = !mLaunchingAffordanceWithPreview; + mFrontTint = Color.TRANSPARENT; - mBehindTint = Color.TRANSPARENT; + mBehindTint = Color.BLACK; mBubbleTint = Color.BLACK; + mBlankScreen = false; - mFrontAlpha = 0f; - mBehindAlpha = mDefaultScrimAlpha; + if (previousState == ScrimState.AOD) { + // Set all scrims black, before they fade transparent. + updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */); + updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */); + if (mScrimForBubble != null) { + updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */); + } + + // Scrims should still be black at the end of the transition. + mFrontTint = Color.BLACK; + mBehindTint = Color.BLACK; + mBubbleTint = Color.BLACK; + mBlankScreen = true; + } + + if (mClipQsScrim) { + updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + } mAnimationDuration = ScrimController.ANIMATION_DURATION; - mBlankScreen = false; } }; 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 e2194ad61b45..98c4af42daaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1045,7 +1045,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationShadeWindowViewController, mNotificationPanelViewController, mAmbientIndicationContainer); - mDozeParameters.addCallback(this::updateLightRevealScrimVisibility); + updateLightRevealScrimVisibility(); mConfigurationController.addCallback(this); @@ -1262,8 +1262,19 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble); mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); - mLightRevealScrim.setRevealAmountListener( - mNotificationShadeWindowController::setLightRevealScrimAmount); + mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> { + Runnable updateOpaqueness = () -> { + mNotificationShadeWindowController.setLightRevealScrimOpaque( + mLightRevealScrim.isScrimOpaque()); + }; + if (opaque) { + // Delay making the view opaque for a frame, because it needs some time to render + // otherwise this can lead to a flicker where the scrim doesn't cover the screen + mLightRevealScrim.post(updateOpaqueness); + } else { + updateOpaqueness.run(); + } + }); mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim); updateLightRevealScrimVisibility(); @@ -4456,8 +4467,11 @@ public class StatusBar extends SystemUI implements DemoMode, || mKeyguardStateController.isKeyguardFadingAway(); // Do not animate the scrim expansion when triggered by the fingerprint sensor. - mScrimController.setExpansionAffectsAlpha( - !mBiometricUnlockController.isBiometricUnlock()); + boolean onKeyguardOrHidingIt = mKeyguardStateController.isShowing() + || mKeyguardStateController.isKeyguardFadingAway() + || mKeyguardStateController.isKeyguardGoingAway(); + mScrimController.setExpansionAffectsAlpha(!(mBiometricUnlockController.isBiometricUnlock() + && onKeyguardOrHidingIt)); boolean launchingAffordanceWithPreview = mNotificationPanelViewController.isLaunchingAffordanceWithPreview(); @@ -4959,11 +4973,5 @@ public class StatusBar extends SystemUI implements DemoMode, } mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha()); - if (mFeatureFlags.useNewLockscreenAnimations() - && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) { - mLightRevealScrim.setVisibility(View.VISIBLE); - } else { - mLightRevealScrim.setVisibility(View.GONE); - } } } 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 3188a522dfad..df445cbeb9e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -53,6 +53,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.FaceAuthScreenBrightnessController; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; @@ -92,7 +93,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // with the appear animations of the PIN/pattern/password views. private static final long NAV_BAR_SHOW_DELAY_BOUNCER = 320; - private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200; + // The duration to fade the nav bar content in/out when the device starts to sleep + private static final long NAV_BAR_CONTENT_FADE_DURATION = 125; // Duration of the Keyguard dismissal animation in case the user is currently locked. This is to // make everything a bit slower to bridge a gap until the user is unlocked and home screen has @@ -194,10 +196,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastGesturalNav; private boolean mLastIsDocked; private boolean mLastPulsing; - private boolean mLastAnimatedToSleep; private int mLastBiometricMode; private boolean mQsExpanded; - private boolean mAnimatedToSleep; private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; @@ -304,8 +304,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Sets a new alt auth interceptor. */ public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) { - mAlternateAuthInterceptor = authInterceptor; - resetAlternateAuth(false); + if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) { + mAlternateAuthInterceptor = authInterceptor; + resetAlternateAuth(false); + } } private void registerListeners() { @@ -318,20 +320,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mDockManager.addListener(mDockEventListener); mIsDocked = mDockManager.isDocked(); } - mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() { - @Override - public void onFinishedWakingUp() { - mAnimatedToSleep = false; - updateStates(); - } - - @Override - public void onFinishedGoingToSleep() { - mAnimatedToSleep = - mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying(); - updateStates(); - } - }); } @Override @@ -564,12 +552,26 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void onStartedWakingUp() { mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() .setAnimationsDisabled(false); + View currentView = getCurrentNavBarView(); + if (currentView != null) { + currentView.animate() + .alpha(1f) + .setDuration(NAV_BAR_CONTENT_FADE_DURATION) + .start(); + } } @Override public void onStartedGoingToSleep() { mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() .setAnimationsDisabled(true); + View currentView = getCurrentNavBarView(); + if (currentView != null) { + currentView.animate() + .alpha(0f) + .setDuration(NAV_BAR_CONTENT_FADE_DURATION) + .start(); + } } @Override @@ -897,7 +899,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public boolean bouncerIsOrWillBeShowing() { - return mBouncer.isShowing() || mBouncer.getShowingSoon(); + return isBouncerShowing() || mBouncer.getShowingSoon(); } public boolean isFullscreenBouncer() { @@ -991,10 +993,28 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastBiometricMode = mBiometricUnlockController.getMode(); mLastGesturalNav = mGesturalNav; mLastIsDocked = mIsDocked; - mLastAnimatedToSleep = mAnimatedToSleep; mStatusBar.onKeyguardViewManagerStatesUpdated(); } + /** + * Updates the visibility of the nav bar content views. + */ + private void updateNavigationBarContentVisibility(boolean navBarContentVisible) { + final NavigationBarView navBarView = mStatusBar.getNavigationBarView(); + if (navBarView != null && navBarView.getCurrentView() != null) { + final View currentView = navBarView.getCurrentView(); + currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE); + } + } + + private View getCurrentNavBarView() { + final NavigationBarView navBarView = mStatusBar.getNavigationBarView(); + return navBarView != null ? navBarView.getCurrentView() : null; + } + + /** + * Updates the visibility of the nav bar window (which will cause insets changes). + */ protected void updateNavigationBarVisibility(boolean navBarVisible) { if (mStatusBar.getNavigationBarView() != null) { if (navBarVisible) { @@ -1022,7 +1042,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING; boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked) && mGesturalNav; - return (!mAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mBouncer.isShowing() + return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav || mGlobalActionsVisible); } @@ -1032,12 +1052,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ protected boolean getLastNavBarVisible() { boolean keyguardShowing = mLastShowing && !mLastOccluded; - boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING; - boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing - || mLastPulsing && !mLastIsDocked) && mLastGesturalNav; - return (!mLastAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mLastBouncerShowing - || mLastRemoteInputActive || keyguardWithGestureNav - || mLastGlobalActionsVisible); + boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING; + boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing + || mLastPulsing && !mLastIsDocked) && mLastGesturalNav; + return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing + || mLastRemoteInputActive || keyguardWithGestureNav + || mLastGlobalActionsVisible); } public boolean shouldDismissOnMenuPressed() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 47deb1f0084b..8821de077ec3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -310,17 +310,11 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, private void onNotificationRemoved(String key, StatusBarNotification old, int reason) { if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); - if (old != null) { - if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() - && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { - if (mStatusBarStateController.getState() == StatusBarState.SHADE - && reason != NotificationListenerService.REASON_CLICK) { - mCommandQueue.animateCollapsePanels(); - } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED + if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() + && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded() + && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED && !isCollapsing()) { - mStatusBarStateController.setState(StatusBarState.KEYGUARD); - } - } + mStatusBarStateController.setState(StatusBarState.KEYGUARD); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 6b52dca42eda..e3f4b03dc4f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -5,7 +5,9 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration +import android.database.ContentObserver import android.os.Handler +import android.provider.Settings import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton @@ -19,6 +21,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject /** @@ -46,15 +49,19 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val statusBarStateControllerImpl: StatusBarStateControllerImpl, private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, private val keyguardStateController: KeyguardStateController, - private val dozeParameters: dagger.Lazy<DozeParameters> + private val dozeParameters: dagger.Lazy<DozeParameters>, + private val globalSettings: GlobalSettings ) : WakefulnessLifecycle.Observer { private val handler = Handler() private lateinit var statusBar: StatusBar private lateinit var lightRevealScrim: LightRevealScrim + private var animatorDurationScale = 1f + private var shouldAnimateInKeyguard = false private var lightRevealAnimationPlaying = false private var aodUiAnimationPlaying = false + private var callbacks = HashSet<Callback>() /** * The result of our decision whether to play the screen off animation in @@ -66,11 +73,17 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply { duration = LIGHT_REVEAL_ANIMATION_DURATION interpolator = Interpolators.LINEAR - addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float } + addUpdateListener { + lightRevealScrim.revealAmount = it.animatedValue as Float + sendUnlockedScreenOffProgressUpdate( + 1f - (it.animatedFraction as Float), + 1f - (it.animatedValue as Float)) + } addListener(object : AnimatorListenerAdapter() { override fun onAnimationCancel(animation: Animator?) { lightRevealScrim.revealAmount = 1f lightRevealAnimationPlaying = false + sendUnlockedScreenOffProgressUpdate(0f, 0f) } override fun onAnimationEnd(animation: Animator?) { @@ -79,6 +92,12 @@ class UnlockedScreenOffAnimationController @Inject constructor( }) } + val animatorDurationScaleObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + updateAnimatorDurationScale() + } + } + fun initialize( statusBar: StatusBar, lightRevealScrim: LightRevealScrim @@ -86,14 +105,25 @@ class UnlockedScreenOffAnimationController @Inject constructor( this.lightRevealScrim = lightRevealScrim this.statusBar = statusBar + updateAnimatorDurationScale() + globalSettings.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), + /* notify for descendants */ false, + animatorDurationScaleObserver) wakefulnessLifecycle.addObserver(this) } + fun updateAnimatorDurationScale() { + animatorDurationScale = + globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) + } + /** * Animates in the provided keyguard view, ending in the same position that it will be in on * AOD. */ fun animateInKeyguard(keyguardView: View, after: Runnable) { + shouldAnimateInKeyguard = false keyguardView.alpha = 0f keyguardView.visibility = View.VISIBLE @@ -115,22 +145,27 @@ class UnlockedScreenOffAnimationController @Inject constructor( .setDuration(duration.toLong()) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(1f) - .withEndAction { - aodUiAnimationPlaying = false + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + aodUiAnimationPlaying = false - // Lock the keyguard if it was waiting for the screen off animation to end. - keyguardViewMediatorLazy.get().maybeHandlePendingLock() + // Lock the keyguard if it was waiting for the screen off animation to end. + keyguardViewMediatorLazy.get().maybeHandlePendingLock() - // Tell the StatusBar to become keyguard for real - we waited on that since it - // is slow and would have caused the animation to jank. - statusBar.updateIsKeyguard() + // Tell the StatusBar to become keyguard for real - we waited on that since + // it is slow and would have caused the animation to jank. + statusBar.updateIsKeyguard() - // Run the callback given to us by the KeyguardVisibilityHelper. - after.run() + // Run the callback given to us by the KeyguardVisibilityHelper. + after.run() - // Done going to sleep, reset this flag. - decidedToAnimateGoingToSleep = null - } + // Done going to sleep, reset this flag. + decidedToAnimateGoingToSleep = null + + // We need to unset the listener. These are persistent for future animators + keyguardView.animate().setListener(null) + } + }) .start() } @@ -138,6 +173,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // Waking up, so reset this flag. decidedToAnimateGoingToSleep = null + shouldAnimateInKeyguard = false lightRevealAnimator.cancel() handler.removeCallbacksAndMessages(null) } @@ -146,7 +182,6 @@ class UnlockedScreenOffAnimationController @Inject constructor( // Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other // observers (such as StatusBar) can ask us whether we were playing the screen off animation // and reset accordingly. - lightRevealAnimationPlaying = false aodUiAnimationPlaying = false // If we can't control the screen off animation, we shouldn't mess with the StatusBar's @@ -167,15 +202,15 @@ class UnlockedScreenOffAnimationController @Inject constructor( if (dozeParameters.get().shouldControlUnlockedScreenOff()) { decidedToAnimateGoingToSleep = true + shouldAnimateInKeyguard = true lightRevealAnimationPlaying = true lightRevealAnimator.start() - handler.postDelayed({ aodUiAnimationPlaying = true // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard. statusBar.notificationPanelViewController.showAodUi() - }, ANIMATE_IN_KEYGUARD_DELAY) + }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong()) } else { decidedToAnimateGoingToSleep = false } @@ -220,7 +255,21 @@ class UnlockedScreenOffAnimationController @Inject constructor( return true } - /** + fun addCallback(callback: Callback) { + callbacks.add(callback) + } + + fun removeCallback(callback: Callback) { + callbacks.remove(callback) + } + + fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) { + callbacks.forEach { + it.onUnlockedScreenOffProgressUpdate(linear, eased) + } + } + +/** * Whether we're doing the light reveal animation or we're done with that and animating in the * AOD UI. */ @@ -228,6 +277,10 @@ class UnlockedScreenOffAnimationController @Inject constructor( return lightRevealAnimationPlaying || aodUiAnimationPlaying } + fun shouldAnimateInKeyguard(): Boolean { + return shouldAnimateInKeyguard + } + /** * Whether the light reveal animation is playing. The second part of the screen off animation, * where AOD animates in, might still be playing if this returns false. @@ -235,4 +288,8 @@ class UnlockedScreenOffAnimationController @Inject constructor( fun isScreenOffLightRevealAnimationPlaying(): Boolean { return lightRevealAnimationPlaying } -}
\ No newline at end of file + + interface Callback { + fun onUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 6982631766f7..80a0a9824589 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -104,7 +104,16 @@ class OngoingCallController @Inject constructor( } } + // Fix for b/199600334 + override fun onEntryCleanUp(entry: NotificationEntry) { + removeChipIfNeeded(entry) + } + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + removeChipIfNeeded(entry) + } + + private fun removeChipIfNeeded(entry: NotificationEntry) { if (entry.sbn.key == callNotificationInfo?.key) { removeChip() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java index ab58286859cc..6d6320e6962d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java @@ -40,6 +40,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.wifitrackerlib.MergedCarrierEntry; import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiPickerTracker; @@ -68,6 +69,7 @@ public class AccessPointControllerImpl private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>(); private final UserManager mUserManager; + private final UserTracker mUserTracker; private final Executor mMainExecutor; private @Nullable WifiPickerTracker mWifiPickerTracker; @@ -84,6 +86,7 @@ public class AccessPointControllerImpl WifiPickerTrackerFactory wifiPickerTrackerFactory ) { mUserManager = userManager; + mUserTracker = userTracker; mCurrentUser = userTracker.getUserId(); mMainExecutor = mainExecutor; mWifiPickerTrackerFactory = wifiPickerTrackerFactory; @@ -118,6 +121,11 @@ public class AccessPointControllerImpl new UserHandle(mCurrentUser)); } + public boolean canConfigMobileData() { + return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, + UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin(); + } + public void onUserSwitched(int newUserId) { mCurrentUser = newUserId; } @@ -157,6 +165,15 @@ public class AccessPointControllerImpl } @Override + public MergedCarrierEntry getMergedCarrierEntry() { + if (mWifiPickerTracker == null) { + fireAcccessPointsCallback(Collections.emptyList()); + return null; + } + return mWifiPickerTracker.getMergedCarrierEntry(); + } + + @Override public int getIcon(WifiEntry ap) { int level = ap.getLevel(); return ICONS[Math.max(0, level)]; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index a0edc7c494bc..1e5251196379 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -149,7 +149,7 @@ public class BrightnessMirrorController private void reinflate() { int index = mStatusBarWindow.indexOfChild(mBrightnessMirror); mStatusBarWindow.removeView(mBrightnessMirror); - mBrightnessMirror = (FrameLayout) LayoutInflater.from(mBrightnessMirror.getContext()) + mBrightnessMirror = (FrameLayout) LayoutInflater.from(mStatusBarWindow.getContext()) .inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false); mToggleSliderController = setMirrorLayout(); mStatusBarWindow.addView(mBrightnessMirror, index); 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 5e70d0dbc418..d838a05135e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -27,6 +27,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardVisibilityHelper; @@ -56,7 +57,7 @@ import javax.inject.Provider; * Manages the user switch on the Keyguard that is used for opening the QS user panel. */ @KeyguardUserSwitcherScope -public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> { +public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> { private static final String TAG = "KeyguardQsUserSwitchController"; private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -76,6 +77,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; private final KeyguardUserDetailAdapter mUserDetailAdapter; private NotificationPanelViewController mNotificationPanelViewController; + private UserAvatarView mUserAvatarView; UserSwitcherController.UserRecord mCurrentUser; // State info for the user switch and keyguard @@ -111,7 +113,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie @Inject public KeyguardQsUserSwitchController( - UserAvatarView view, + FrameLayout view, Context context, @Main Resources resources, ScreenLifecycle screenLifecycle, @@ -143,6 +145,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie protected void onInit() { super.onInit(); if (DEBUG) Log.d(TAG, "onInit"); + mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { @@ -150,11 +153,10 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie } }; - mView.setOnClickListener(v -> { + mUserAvatarView.setOnClickListener(v -> { if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return; } - if (isListAnimating()) { return; } @@ -163,7 +165,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie openQsUserPanel(); }); - mView.setAccessibilityDelegate(new View.AccessibilityDelegate() { + mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); info.addAction(new AccessibilityNodeInfo.AccessibilityAction( @@ -237,12 +239,12 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie R.string.accessibility_multi_user_switch_switcher); } - if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) { - mView.setContentDescription(contentDescription); + if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) { + mUserAvatarView.setContentDescription(contentDescription); } int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL; - mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId); + mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId); } Drawable getCurrentUserIcon() { @@ -269,7 +271,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie * Get the height of the keyguard user switcher view when closed. */ public int getUserIconHeight() { - return mView.getHeight(); + return mUserAvatarView.getHeight(); } /** 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 20b66f03192e..cd8894cae684 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java @@ -103,7 +103,7 @@ public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout { } } - if (animate) { + if (animate && userItemViews.length > 1) { // 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. 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 dc5ecd3f1cca..be2bf0750d92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -23,6 +23,7 @@ import android.telephony.SubscriptionInfo; import com.android.settingslib.net.DataUsageController; import com.android.systemui.demomode.DemoMode; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.wifitrackerlib.MergedCarrierEntry; import com.android.wifitrackerlib.WifiEntry; import java.util.List; @@ -226,9 +227,11 @@ public interface NetworkController extends CallbackController<SignalCallback>, D void addAccessPointCallback(AccessPointCallback callback); void removeAccessPointCallback(AccessPointCallback callback); void scanForAccessPoints(); + MergedCarrierEntry getMergedCarrierEntry(); int getIcon(WifiEntry ap); boolean connect(WifiEntry ap); boolean canConfigWifi(); + boolean canConfigMobileData(); public interface AccessPointCallback { void onAccessPointsChanged(List<WifiEntry> accessPoints); 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 d0eef85458c0..ec8a68f914dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -70,9 +70,12 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.qs.tiles.dialog.InternetDialogUtil; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; @@ -193,6 +196,8 @@ public class NetworkControllerImpl extends BroadcastReceiver private boolean mUserSetup; private boolean mSimDetected; private boolean mForceCellularValidated; + private InternetDialogFactory mInternetDialogFactory; + private Handler mMainHandler; @VisibleForTesting FiveGServiceClient mFiveGServiceClient; @@ -226,7 +231,9 @@ public class NetworkControllerImpl extends BroadcastReceiver DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags, - DumpManager dumpManager) { + DumpManager dumpManager, + @Main Handler handler, + InternetDialogFactory internetDialogFactory) { this(context, connectivityManager, telephonyManager, telephonyListenerManager, @@ -247,6 +254,8 @@ public class NetworkControllerImpl extends BroadcastReceiver featureFlags, dumpManager); mReceiverHandler.post(mRegisterListeners); + mMainHandler = handler; + mInternetDialogFactory = internetDialogFactory; } @VisibleForTesting @@ -488,6 +497,9 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); + if (InternetDialogUtil.isProviderModelEnabled(mContext)) { + filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY); + } filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; @@ -798,6 +810,10 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = Config.readConfig(mContext); mReceiverHandler.post(this::handleConfigurationChanged); break; + case Settings.Panel.ACTION_INTERNET_CONNECTIVITY: + mMainHandler.post(() -> mInternetDialogFactory.create(true, + mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi())); + break; default: int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java index 22fd93ed10ed..2d47c8f0b577 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java @@ -71,7 +71,11 @@ public class NextAlarmControllerImpl extends BroadcastReceiver if (mNextAlarm != null) { pw.println(new Date(mNextAlarm.getTriggerTime())); pw.print(" PendingIntentPkg="); - pw.println(mNextAlarm.getShowIntent().getCreatorPackage()); + if (mNextAlarm.getShowIntent() != null) { + pw.println(mNextAlarm.getShowIntent().getCreatorPackage()); + } else { + pw.println("showIntent=null"); + } } else { pw.println("null"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 84d7c05ddc14..5d7d4809dd57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -77,7 +77,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -204,7 +203,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene final int stroke = colorized ? mContext.getResources().getDimensionPixelSize( R.dimen.remote_input_view_text_stroke) : 0; if (colorized) { - final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor); + final boolean dark = Notification.Builder.isColorDark(backgroundColor); final int foregroundColor = dark ? Color.WHITE : Color.BLACK; final int inverseColor = dark ? Color.BLACK : Color.WHITE; editBgColor = backgroundColor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java index f258fb19ff7d..1158324567ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java @@ -23,6 +23,7 @@ public interface RotationLockController extends Listenable, int getRotationLockOrientation(); boolean isRotationLockAffordanceVisible(); boolean isRotationLocked(); + boolean isCameraRotationEnabled(); void setRotationLocked(boolean locked); void setRotationLockedAtAngle(boolean locked, int rotation); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java index 53d68d0ff0ac..c185928998c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java @@ -18,11 +18,13 @@ package com.android.systemui.statusbar.policy; import android.content.Context; import android.os.UserHandle; +import android.provider.Settings.Secure; import androidx.annotation.NonNull; import com.android.internal.view.RotationPolicy; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.util.settings.SecureSettings; import java.util.concurrent.CopyOnWriteArrayList; @@ -32,20 +34,22 @@ import javax.inject.Inject; @SysUISingleton public final class RotationLockControllerImpl implements RotationLockController { private final Context mContext; + private final SecureSettings mSecureSettings; private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks = new CopyOnWriteArrayList<RotationLockControllerCallback>(); private final RotationPolicy.RotationPolicyListener mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() { - @Override - public void onChange() { - notifyChanged(); - } - }; + @Override + public void onChange() { + notifyChanged(); + } + }; @Inject - public RotationLockControllerImpl(Context context) { + public RotationLockControllerImpl(Context context, SecureSettings secureSettings) { mContext = context; + mSecureSettings = secureSettings; setListening(true); } @@ -68,11 +72,16 @@ public final class RotationLockControllerImpl implements RotationLockController return RotationPolicy.isRotationLocked(mContext); } + public boolean isCameraRotationEnabled() { + return mSecureSettings.getIntForUser(Secure.CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT) + == 1; + } + public void setRotationLocked(boolean locked) { RotationPolicy.setRotationLock(mContext, locked); } - public void setRotationLockedAtAngle(boolean locked, int rotation){ + public void setRotationLockedAtAngle(boolean locked, int rotation) { RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation); } 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 41b1dd12639a..4e33529f3c36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -628,7 +628,7 @@ public class SmartReplyView extends ViewGroup { mCurrentBackgroundColor = backgroundColor; mCurrentColorized = colorized; - final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor); + final boolean dark = Notification.Builder.isColorDark(backgroundColor); mCurrentTextColor = ContrastColorUtil.ensureTextContrast( dark ? mDefaultTextColorDarkBg : mDefaultTextColor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt new file mode 100644 index 000000000000..ae9d9ee445f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.content.Context +import android.text.StaticLayout +import android.util.AttributeSet +import android.widget.TextView +import com.android.systemui.R + +/** + * View for showing a date that can toggle between two different formats depending on size. + * + * If no pattern can fit, it will display empty. + * + * @see R.styleable.VariableDateView_longDatePattern + * @see R.styleable.VariableDateView_shortDatePattern + */ +class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context, attrs) { + + val longerPattern: String + val shorterPattern: String + + init { + val a = context.theme.obtainStyledAttributes( + attrs, + R.styleable.VariableDateView, + 0, 0) + longerPattern = a.getString(R.styleable.VariableDateView_longDatePattern) + ?: context.getString(R.string.system_ui_date_pattern) + shorterPattern = a.getString(R.styleable.VariableDateView_shortDatePattern) + ?: context.getString(R.string.abbrev_month_day_no_year) + + a.recycle() + } + + /** + * Freeze the pattern switching + * + * Use during animations if the container will change its size but this view should not change + */ + var freezeSwitching = false + + private var onMeasureListener: OnMeasureListener? = null + + fun onAttach(listener: OnMeasureListener?) { + onMeasureListener = listener + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd + if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) { + onMeasureListener?.onMeasureAction(availableWidth) + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + fun getDesiredWidthForText(text: CharSequence): Float { + return StaticLayout.getDesiredWidth(text, paint) + } + + interface OnMeasureListener { + fun onMeasureAction(availableWidth: Int) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt new file mode 100644 index 000000000000..99d84c4d0ced --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.icu.text.DateFormat +import android.icu.text.DisplayContext +import android.icu.util.Calendar +import android.os.Handler +import android.os.HandlerExecutor +import android.os.UserHandle +import android.text.TextUtils +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.systemui.Dependency +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.util.ViewController +import com.android.systemui.util.time.SystemClock +import java.text.FieldPosition +import java.text.ParsePosition +import java.util.Date +import java.util.Locale +import javax.inject.Inject +import javax.inject.Named + +@VisibleForTesting +internal fun getTextForFormat(date: Date?, format: DateFormat): String { + return if (format === EMPTY_FORMAT) { // Check if same object + "" + } else format.format(date) +} + +@VisibleForTesting +internal fun getFormatFromPattern(pattern: String?): DateFormat { + if (TextUtils.equals(pattern, "")) { + return EMPTY_FORMAT + } + val l = Locale.getDefault() + val format = DateFormat.getInstanceForSkeleton(pattern, l) + format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE) + return format +} + +private val EMPTY_FORMAT: DateFormat = object : DateFormat() { + override fun format( + cal: Calendar, + toAppendTo: StringBuffer, + fieldPosition: FieldPosition + ): StringBuffer? { + return null + } + + override fun parse(text: String, cal: Calendar, pos: ParsePosition) {} +} + +private const val DEBUG = false +private const val TAG = "VariableDateViewController" + +class VariableDateViewController( + private val systemClock: SystemClock, + private val broadcastDispatcher: BroadcastDispatcher, + private val timeTickHandler: Handler, + view: VariableDateView +) : ViewController<VariableDateView>(view) { + + private var dateFormat: DateFormat? = null + private var datePattern = view.longerPattern + set(value) { + if (field == value) return + field = value + dateFormat = null + if (isAttachedToWindow) { + post(::updateClock) + } + } + private var lastWidth = Integer.MAX_VALUE + private var lastText = "" + private var currentTime = Date() + + // View class easy accessors + private val longerPattern: String + get() = mView.longerPattern + private val shorterPattern: String + get() = mView.shorterPattern + private fun post(block: () -> Unit) = mView.handler?.post(block) + + private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + // If the handler is null, it means we received a broadcast while the view has not + // finished being attached or in the process of being detached. + // In that case, do not post anything. + val handler = mView.handler ?: return + val action = intent.action + if ( + Intent.ACTION_TIME_TICK == action || + Intent.ACTION_TIME_CHANGED == action || + Intent.ACTION_TIMEZONE_CHANGED == action || + Intent.ACTION_LOCALE_CHANGED == action + ) { + if ( + Intent.ACTION_LOCALE_CHANGED == action || + Intent.ACTION_TIMEZONE_CHANGED == action + ) { + // need to get a fresh date format + handler.post { dateFormat = null } + } + handler.post(::updateClock) + } + } + } + + private val onMeasureListener = object : VariableDateView.OnMeasureListener { + override fun onMeasureAction(availableWidth: Int) { + if (availableWidth != lastWidth) { + // maybeChangeFormat will post if the pattern needs to change. + maybeChangeFormat(availableWidth) + lastWidth = availableWidth + } + } + } + + override fun onViewAttached() { + val filter = IntentFilter().apply { + addAction(Intent.ACTION_TIME_TICK) + addAction(Intent.ACTION_TIME_CHANGED) + addAction(Intent.ACTION_TIMEZONE_CHANGED) + addAction(Intent.ACTION_LOCALE_CHANGED) + } + + broadcastDispatcher.registerReceiver(intentReceiver, filter, + HandlerExecutor(timeTickHandler), UserHandle.SYSTEM) + + post(::updateClock) + mView.onAttach(onMeasureListener) + } + + override fun onViewDetached() { + dateFormat = null + mView.onAttach(null) + broadcastDispatcher.unregisterReceiver(intentReceiver) + } + + private fun updateClock() { + if (dateFormat == null) { + dateFormat = getFormatFromPattern(datePattern) + } + + currentTime.time = systemClock.currentTimeMillis() + + val text = getTextForFormat(currentTime, dateFormat!!) + if (text != lastText) { + mView.setText(text) + lastText = text + } + } + + private fun maybeChangeFormat(availableWidth: Int) { + if (mView.freezeSwitching || + availableWidth > lastWidth && datePattern == longerPattern || + availableWidth < lastWidth && datePattern == "" + ) { + // Nothing to do + return + } + if (DEBUG) Log.d(TAG, "Width changed. Maybe changing pattern") + // Start with longer pattern and see what fits + var text = getTextForFormat(currentTime, getFormatFromPattern(longerPattern)) + var length = mView.getDesiredWidthForText(text) + if (length <= availableWidth) { + changePattern(longerPattern) + return + } + + text = getTextForFormat(currentTime, getFormatFromPattern(shorterPattern)) + length = mView.getDesiredWidthForText(text) + if (length <= availableWidth) { + changePattern(shorterPattern) + return + } + + changePattern("") + } + + private fun changePattern(newPattern: String) { + if (newPattern.equals(datePattern)) return + if (DEBUG) Log.d(TAG, "Changing pattern to $newPattern") + datePattern = newPattern + } + + class Factory @Inject constructor( + private val systemClock: SystemClock, + private val broadcastDispatcher: BroadcastDispatcher, + @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler + ) { + fun create(view: VariableDateView): VariableDateViewController { + return VariableDateViewController( + systemClock, + broadcastDispatcher, + handler, + view + ) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index c3b4fbe9a13d..e2d0bb9991ed 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -132,14 +132,18 @@ public class ThemeOverlayApplier implements Dumpable { /* Target package for each overlay category. */ private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>(); private final OverlayManager mOverlayManager; - private final Executor mExecutor; + private final Executor mBgExecutor; + private final Executor mMainExecutor; private final String mLauncherPackage; private final String mThemePickerPackage; - public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor, + public ThemeOverlayApplier(OverlayManager overlayManager, + Executor bgExecutor, + Executor mainExecutor, String launcherPackage, String themePickerPackage, DumpManager dumpManager) { mOverlayManager = overlayManager; - mExecutor = executor; + mBgExecutor = bgExecutor; + mMainExecutor = mainExecutor; mLauncherPackage = launcherPackage; mThemePickerPackage = themePickerPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( @@ -170,12 +174,12 @@ public class ThemeOverlayApplier implements Dumpable { * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that * affect sysui will also be applied to the system user. */ - void applyCurrentUserOverlays( + public void applyCurrentUserOverlays( Map<String, OverlayIdentifier> categoryToPackage, FabricatedOverlay[] pendingCreation, int currentUser, Set<UserHandle> managedProfiles) { - mExecutor.execute(() -> { + mBgExecutor.execute(() -> { // Disable all overlays that have not been specified in the user setting. final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index 98b4209ede00..bfa50bcee270 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -36,6 +36,7 @@ import android.os.UserHandle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.TextView; @@ -63,6 +64,8 @@ public class UsbPermissionActivity extends AlertActivity public void onCreate(Bundle icicle) { super.onCreate(icicle); + getWindow().addPrivateFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); Intent intent = getIntent(); mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java index de5a3637fe9f..14190fa752c3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java +++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java @@ -23,8 +23,9 @@ import android.content.IntentFilter; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; -import android.util.SparseArray; +import android.util.SparseBooleanArray; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import javax.inject.Inject; @@ -34,39 +35,54 @@ import javax.inject.Inject; */ @SysUISingleton public class CarrierConfigTracker extends BroadcastReceiver { - private final SparseArray<Boolean> mCallStrengthConfigs = new SparseArray<>(); - private final SparseArray<Boolean> mNoCallingConfigs = new SparseArray<>(); + private final SparseBooleanArray mCallStrengthConfigs = new SparseBooleanArray(); + private final SparseBooleanArray mNoCallingConfigs = new SparseBooleanArray(); + private final SparseBooleanArray mCarrierProvisionsWifiMergedNetworks = + new SparseBooleanArray(); private final CarrierConfigManager mCarrierConfigManager; private boolean mDefaultCallStrengthConfigLoaded; private boolean mDefaultCallStrengthConfig; private boolean mDefaultNoCallingConfigLoaded; private boolean mDefaultNoCallingConfig; + private boolean mDefaultCarrierProvisionsWifiMergedNetworksLoaded; + private boolean mDefaultCarrierProvisionsWifiMergedNetworks; @Inject - public CarrierConfigTracker(Context context) { + public CarrierConfigTracker(Context context, BroadcastDispatcher broadcastDispatcher) { mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class); - context.registerReceiver( + broadcastDispatcher.registerReceiver( this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)); } @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction() == CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) { - int subId = intent.getIntExtra( - CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - return; - } - PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId); - if (b != null) { - boolean hideNoCallingConfig = b.getBoolean( - CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL); - boolean displayCallStrengthIcon = b.getBoolean( - CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL); - mCallStrengthConfigs.put(subId, displayCallStrengthIcon); - mNoCallingConfigs.put(subId, hideNoCallingConfig); - } + if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) { + return; + } + + final int subId = intent.getIntExtra( + CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + return; + } + + final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId); + if (config == null) { + return; + } + + synchronized (mCallStrengthConfigs) { + mCallStrengthConfigs.put(subId, config.getBoolean( + CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL)); + } + synchronized (mNoCallingConfigs) { + mNoCallingConfigs.put(subId, config.getBoolean( + CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL)); + } + synchronized (mCarrierProvisionsWifiMergedNetworks) { + mCarrierProvisionsWifiMergedNetworks.put(subId, config.getBoolean( + CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL)); } } @@ -74,8 +90,10 @@ public class CarrierConfigTracker extends BroadcastReceiver { * Returns the KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL value for the given subId. */ public boolean getCallStrengthConfig(int subId) { - if (mCallStrengthConfigs.indexOfKey(subId) >= 0) { - return mCallStrengthConfigs.get(subId); + synchronized (mCallStrengthConfigs) { + if (mCallStrengthConfigs.indexOfKey(subId) >= 0) { + return mCallStrengthConfigs.get(subId); + } } if (!mDefaultCallStrengthConfigLoaded) { mDefaultCallStrengthConfig = @@ -90,8 +108,10 @@ public class CarrierConfigTracker extends BroadcastReceiver { * Returns the KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL value for the given subId. */ public boolean getNoCallingConfig(int subId) { - if (mNoCallingConfigs.indexOfKey(subId) >= 0) { - return mNoCallingConfigs.get(subId); + synchronized (mNoCallingConfigs) { + if (mNoCallingConfigs.indexOfKey(subId) >= 0) { + return mNoCallingConfigs.get(subId); + } } if (!mDefaultNoCallingConfigLoaded) { mDefaultNoCallingConfig = @@ -101,4 +121,22 @@ public class CarrierConfigTracker extends BroadcastReceiver { } return mDefaultNoCallingConfig; } + + /** + * Returns the KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL value for the given subId. + */ + public boolean getCarrierProvisionsWifiMergedNetworksBool(int subId) { + synchronized (mCarrierProvisionsWifiMergedNetworks) { + if (mCarrierProvisionsWifiMergedNetworks.indexOfKey(subId) >= 0) { + return mCarrierProvisionsWifiMergedNetworks.get(subId); + } + } + if (!mDefaultCarrierProvisionsWifiMergedNetworksLoaded) { + mDefaultCarrierProvisionsWifiMergedNetworks = + CarrierConfigManager.getDefaultConfig().getBoolean( + CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL); + mDefaultCarrierProvisionsWifiMergedNetworksLoaded = true; + } + return mDefaultCarrierProvisionsWifiMergedNetworks; + } } 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 90e022a52d7a..bd1103982017 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -87,15 +87,23 @@ public class ProximitySensor implements ThresholdSensor { && (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow() || !event.getBelow())) { - mSecondaryThresholdSensor.pause(); + chooseSensor(); if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) { // Only check the secondary as long as the primary thinks we're near. - mCancelSecondaryRunnable = null; + if (mCancelSecondaryRunnable != null) { + mCancelSecondaryRunnable.run(); + mCancelSecondaryRunnable = null; + } return; } else { // Check this sensor again in a moment. - mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed( - mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS); + mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> { + // This is safe because we know that mSecondaryThresholdSensor + // is loaded, otherwise we wouldn't be here. + mPrimaryThresholdSensor.pause(); + mSecondaryThresholdSensor.resume(); + }, + SECONDARY_PING_INTERVAL_MS); } } logDebug("Secondary sensor event: " + event.getBelow() + "."); @@ -159,12 +167,8 @@ public class ProximitySensor implements ThresholdSensor { * of what is reported by the primary sensor. */ public void setSecondarySafe(boolean safe) { - mSecondarySafe = safe; - if (!mSecondarySafe) { - mSecondaryThresholdSensor.pause(); - } else { - mSecondaryThresholdSensor.resume(); - } + mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe; + chooseSensor(); } /** @@ -209,16 +213,30 @@ public class ProximitySensor implements ThresholdSensor { return; } if (!mInitializedListeners) { + mPrimaryThresholdSensor.pause(); + mSecondaryThresholdSensor.pause(); mPrimaryThresholdSensor.register(mPrimaryEventListener); - if (!mSecondarySafe) { - mSecondaryThresholdSensor.pause(); - } mSecondaryThresholdSensor.register(mSecondaryEventListener); mInitializedListeners = true; } logDebug("Registering sensor listener"); - mPrimaryThresholdSensor.resume(); + mRegistered = true; + chooseSensor(); + } + + private void chooseSensor() { + mExecution.assertIsMainThread(); + if (!mRegistered || mPaused || mListeners.isEmpty()) { + return; + } + if (mSecondarySafe) { + mSecondaryThresholdSensor.resume(); + mPrimaryThresholdSensor.pause(); + } else { + mPrimaryThresholdSensor.resume(); + mSecondaryThresholdSensor.pause(); + } } /** @@ -312,7 +330,7 @@ public class ProximitySensor implements ThresholdSensor { } if (!mSecondarySafe && !event.getBelow()) { - mSecondaryThresholdSensor.pause(); + chooseSensor(); } mLastEvent = event; diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java index 2b4b49b82df1..99177778310d 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java @@ -116,7 +116,7 @@ public class WalletActivity extends LifecycleActivity implements if (toolbar != null) { setActionBar(toolbar); } - setTitle(""); + getActionBar().setDisplayShowTitleEnabled(false); getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeAsUpIndicator(getHomeIndicatorDrawable()); getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close); @@ -220,6 +220,12 @@ public class WalletActivity extends LifecycleActivity implements } @Override + protected void onStop() { + super.onStop(); + finish(); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.wallet_activity_options_menu, menu); return super.onCreateOptionsMenu(menu); |