diff options
132 files changed, 2192 insertions, 1076 deletions
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index edcab29dec94..0ff9f6655b8a 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -431,7 +431,7 @@ public class ActivityOptions { private boolean mOverrideTaskTransition; private String mSplashScreenThemeResName; @SplashScreen.SplashScreenStyle - private int mSplashScreenStyle; + private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED; private boolean mRemoveWithTaskOrganizer; private boolean mLaunchedFromBubble; private boolean mTransientLaunch; diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index fca4c698c49c..3712caeddaf5 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1488,18 +1488,27 @@ public class WallpaperManager { mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - boolean ok = false; + final Bitmap tmp = BitmapFactory.decodeStream(resources.openRawResource(resid)); try { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - copyStreamToWallpaperFile(resources.openRawResource(resid), fos); - // The 'close()' is the trigger for any server-side image manipulation, - // so we must do that before waiting for completion. - fos.close(); - completion.waitForCompletion(); + // If the stream can't be decoded, treat it as an invalid input. + if (tmp != null) { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); + // The 'close()' is the trigger for any server-side image manipulation, + // so we must do that before waiting for completion. + fos.close(); + completion.waitForCompletion(); + } else { + throw new IllegalArgumentException( + "Resource 0x" + Integer.toHexString(resid) + " is invalid"); + } } finally { // Might be redundant but completion shouldn't wait unless the write // succeeded; this is a fallback if it threw past the close+wait. IoUtils.closeQuietly(fos); + if (tmp != null) { + tmp.recycle(); + } } } } catch (RemoteException e) { @@ -1741,13 +1750,22 @@ public class WallpaperManager { result, which, completion, mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; + final Bitmap tmp = BitmapFactory.decodeStream(bitmapData); try { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - copyStreamToWallpaperFile(bitmapData, fos); - fos.close(); - completion.waitForCompletion(); + // If the stream can't be decoded, treat it as an invalid input. + if (tmp != null) { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.close(); + completion.waitForCompletion(); + } else { + throw new IllegalArgumentException("InputStream is invalid"); + } } finally { IoUtils.closeQuietly(fos); + if (tmp != null) { + tmp.recycle(); + } } } } catch (RemoteException e) { diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index b5298fc9e1c7..8daf9f0450c4 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1010,12 +1010,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * This change id restricts treatments that force a given min aspect ratio to activities * whose orientation is fixed to portrait. * - * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled. + * This treatment is enabled by default and only takes effect if OVERRIDE_MIN_ASPECT_RATIO is + * also enabled. * @hide */ @ChangeId @Overridable - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2) @TestApi public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 43ef33e1f420..28046c56b9f8 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -151,6 +151,12 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_RE_ENROLL = 16; /** + * The privacy setting has been enabled and will block use of the sensor. + * @hide + */ + int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18; + + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index fe43c83d17f1..fd46f243874b 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -69,7 +69,7 @@ public interface BiometricFaceConstants { BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, BIOMETRIC_ERROR_RE_ENROLL, - FACE_ERROR_UNKNOWN + FACE_ERROR_UNKNOWN, }) @Retention(RetentionPolicy.SOURCE) @interface FaceError {} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fdd3836473ba..07feb44efea9 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -953,6 +953,22 @@ public final class Settings { public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS"; /** + * Activity Action: Show settings to allow pairing bluetooth devices. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_BLUETOOTH_PAIRING_SETTINGS = + "android.settings.BLUETOOTH_PAIRING_SETTINGS"; + + /** * Activity Action: Show settings to configure input methods, in particular * allowing the user to enable input methods. * <p> diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index 046232a8afaf..2dac81c66d2a 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -129,7 +129,11 @@ public class RemoteAnimationTarget implements Parcelable { * The index of the element in the tree in prefix order. This should be used for z-layering * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to * happen. + * @deprecated WindowManager may set a z-order different from the prefix order, and has set the + * correct layer for the animation leash already, so this should not be used for + * layer any more. */ + @Deprecated @UnsupportedAppUsage public final int prefixOrderIndex; diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index b9bf009fb2bb..090dbff488e9 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -43,6 +43,11 @@ import java.util.ArrayList; */ public interface SplashScreen { /** + * The splash screen style is not defined. + * @hide + */ + int SPLASH_SCREEN_STYLE_UNDEFINED = -1; + /** * Force splash screen to be empty. * @hide */ @@ -55,6 +60,7 @@ public interface SplashScreen { /** @hide */ @IntDef(prefix = { "SPLASH_SCREEN_STYLE_" }, value = { + SPLASH_SCREEN_STYLE_UNDEFINED, SPLASH_SCREEN_STYLE_EMPTY, SPLASH_SCREEN_STYLE_ICON }) diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 424632fe82eb..6541b14b9070 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -270,8 +270,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Drawable mCaptionBackgroundDrawable; private Drawable mUserCaptionBackgroundDrawable; - private float mAvailableWidth; - String mLogTag = TAG; private final Rect mFloatingInsets = new Rect(); private boolean mApplyFloatingVerticalInsets = false; @@ -315,8 +313,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mSemiTransparentBarColor = context.getResources().getColor( R.color.system_bar_background_semi_transparent, null /* theme */); - updateAvailableWidth(); - setWindow(window); updateLogTag(params); @@ -697,7 +693,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + final Resources res = getContext().getResources(); + final DisplayMetrics metrics = res.getDisplayMetrics(); final boolean isPortrait = getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; @@ -767,17 +764,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (!fixedWidth && widthMode == AT_MOST) { final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor; + final float availableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + res.getConfiguration().screenWidthDp, metrics); if (tv.type != TypedValue.TYPE_NULL) { final int min; if (tv.type == TypedValue.TYPE_DIMENSION) { - min = (int)tv.getDimension(metrics); + min = (int) tv.getDimension(metrics); } else if (tv.type == TypedValue.TYPE_FRACTION) { - min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth); + min = (int) tv.getFraction(availableWidth, availableWidth); } else { min = 0; } if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::" - + tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth); + + tv.coerceToString() + ", mAvailableWidth=" + availableWidth); if (width < min) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); @@ -2144,7 +2143,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateDecorCaptionStatus(newConfig); - updateAvailableWidth(); initializeElevation(); } @@ -2616,12 +2614,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLogTag = TAG + "[" + getTitleSuffix(params) + "]"; } - private void updateAvailableWidth() { - Resources res = getResources(); - mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - res.getConfiguration().screenWidthDp, res.getDisplayMetrics()); - } - /** * @hide */ diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3c7f0267d943..166d6abd1809 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1666,6 +1666,8 @@ <string name="face_setup_notification_title">Set up Face Unlock</string> <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] --> <string name="face_setup_notification_content">Unlock your phone by looking at it</string> + <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] --> + <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string> <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string> <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a23816e8174a..54282bef00f0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2570,6 +2570,7 @@ <java-symbol type="string" name="face_recalibrate_notification_name" /> <java-symbol type="string" name="face_recalibrate_notification_title" /> <java-symbol type="string" name="face_recalibrate_notification_content" /> + <java-symbol type="string" name="face_sensor_privacy_enabled" /> <java-symbol type="string" name="face_error_unable_to_process" /> <java-symbol type="string" name="face_error_hw_not_available" /> <java-symbol type="string" name="face_error_no_space" /> diff --git a/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml b/core/tests/coretests/res/drawable/custom_drawable.xml index 9a69b33fd591..ebb821fa11fb 100644 --- a/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml +++ b/core/tests/coretests/res/drawable/custom_drawable.xml @@ -1,3 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> <!-- ~ Copyright (C) 2021 The Android Open Source Project ~ @@ -11,14 +12,10 @@ ~ 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 + ~ limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="?android:attr/textColorPrimary" - android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" /> -</vector> + +<drawable xmlns:android="http://schemas.android.com/apk/res/android" + class="android.window.CustomDrawable" + android:drawable="@drawable/bitmap_drawable" + android:inset="10dp" />
\ No newline at end of file diff --git a/core/tests/coretests/src/android/window/CustomDrawable.java b/core/tests/coretests/src/android/window/CustomDrawable.java new file mode 100644 index 000000000000..c25f87782420 --- /dev/null +++ b/core/tests/coretests/src/android/window/CustomDrawable.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.graphics.drawable.InsetDrawable; + +public class CustomDrawable extends InsetDrawable { + public CustomDrawable() { + super(null, 0); + } +} diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java index 83280f18c889..656e756416d0 100644 --- a/core/tests/coretests/src/android/window/WindowContextTest.java +++ b/core/tests/coretests/src/android/window/WindowContextTest.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.app.Activity; @@ -47,6 +48,8 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; +import com.android.frameworks.coretests.R; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -242,6 +245,12 @@ public class WindowContextTest { mInstrumentation.runOnMainSync(() -> wm.addView(subWindow, subWindowAttrs)); } + @Test + public void testGetCustomDrawable() { + assertNotNull(mWindowContext.getResources().getDrawable(R.drawable.custom_drawable, + null /* theme */)); + } + private WindowContext createWindowContext() { return createWindowContext(TYPE_APPLICATION_OVERLAY); } diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java index 850c55166edc..6fa1a694eb67 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -89,7 +89,7 @@ abstract class KeyStoreCryptoOperationUtils { // specific sensor (the one that hasn't changed), and 2) currently the only // signal to developers is the UserNotAuthenticatedException, which doesn't // indicate a specific sensor. - boolean canUnlockViaBiometrics = true; + boolean canUnlockViaBiometrics = biometricSids.length > 0; for (long sid : biometricSids) { if (!keySids.contains(sid)) { canUnlockViaBiometrics = false; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index fe6c7ba3b24c..dc4e27a4c8d8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -91,11 +91,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, - @NonNull Consumer<Exception> failureCallback) { + @Nullable Consumer<Exception> failureCallback) { try { mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule); } catch (Exception e) { - failureCallback.accept(e); + if (failureCallback != null) { + failureCallback.accept(e); + } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 8c8ef92b80dc..46bdf6d0e689 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -16,6 +16,7 @@ package androidx.window.extensions.embedding; +import static android.os.Process.THREAD_PRIORITY_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; @@ -29,7 +30,7 @@ import android.animation.Animator; import android.animation.ValueAnimator; import android.graphics.Rect; import android.os.Handler; -import android.os.Looper; +import android.os.HandlerThread; import android.os.RemoteException; import android.util.Log; import android.view.IRemoteAnimationFinishedCallback; @@ -50,10 +51,14 @@ import java.util.function.BiFunction; class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { private static final String TAG = "TaskFragAnimationRunner"; - private final Handler mHandler = new Handler(Looper.myLooper()); + private final Handler mHandler; private final TaskFragmentAnimationSpec mAnimationSpec; TaskFragmentAnimationRunner() { + HandlerThread animationThread = new HandlerThread( + "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY); + animationThread.start(); + mHandler = animationThread.getThreadHandler(); mAnimationSpec = new TaskFragmentAnimationSpec(mHandler); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index a1a53bc93781..4d2d0551d828 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -103,7 +103,7 @@ class TaskFragmentContainer { ActivityThread activityThread = ActivityThread.currentActivityThread(); for (IBinder token : mInfo.getActivities()) { Activity activity = activityThread.getActivity(token); - if (activity != null && !allActivities.contains(activity)) { + if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) { allActivities.add(activity); } } diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 8e3d726362f2..cdff5858c77d 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -43,6 +43,7 @@ filegroup { name: "wm_shell_util-sources", srcs: [ "src/com/android/wm/shell/util/**/*.java", + "src/com/android/wm/shell/common/split/SplitScreenConstants.java" ], path: "src", } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index 358553d70501..908a31dc3e4e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -16,7 +16,7 @@ package com.android.wm.shell; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index c8449a362988..1f21937b5025 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -21,9 +21,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 3579bf441390..ba343cb12085 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -29,11 +29,13 @@ import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_D import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START; import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR; import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.annotation.IntDef; import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; @@ -61,6 +63,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import java.io.PrintWriter; @@ -69,30 +72,6 @@ import java.io.PrintWriter; * divide position changes. */ public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener { - /** - * Split position isn't specified normally meaning to use what ever it is currently set to. - */ - public static final int SPLIT_POSITION_UNDEFINED = -1; - - /** - * Specifies that a split is positioned at the top half of the screen if - * in portrait mode or at the left half of the screen if in landscape mode. - */ - public static final int SPLIT_POSITION_TOP_OR_LEFT = 0; - - /** - * Specifies that a split is positioned at the bottom half of the screen if - * in portrait mode or at the right half of the screen if in landscape mode. - */ - public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1; - - @IntDef(prefix = {"SPLIT_POSITION_"}, value = { - SPLIT_POSITION_UNDEFINED, - SPLIT_POSITION_TOP_OR_LEFT, - SPLIT_POSITION_BOTTOM_OR_RIGHT - }) - public @interface SplitPosition { - } private final int mDividerWindowWidth; private final int mDividerInsets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java new file mode 100644 index 000000000000..9b614875119b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.common.split; + +import android.annotation.IntDef; + +/** Helper utility class of methods and constants that are available to be imported in Launcher. */ +public class SplitScreenConstants { + + /** + * Split position isn't specified normally meaning to use what ever it is currently set to. + */ + public static final int SPLIT_POSITION_UNDEFINED = -1; + + /** + * Specifies that a split is positioned at the top half of the screen if + * in portrait mode or at the left half of the screen if in landscape mode. + */ + public static final int SPLIT_POSITION_TOP_OR_LEFT = 0; + + /** + * Specifies that a split is positioned at the bottom half of the screen if + * in portrait mode or at the right half of the screen if in landscape mode. + */ + public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1; + + @IntDef(prefix = {"SPLIT_POSITION_"}, value = { + SPLIT_POSITION_UNDEFINED, + SPLIT_POSITION_TOP_OR_LEFT, + SPLIT_POSITION_BOTTOM_OR_RIGHT + }) + public @interface SplitPosition { + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index d2bd28e0f753..b65a2e4ffca6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -29,9 +29,9 @@ import static android.content.Intent.EXTRA_SHORTCUT_ID; import static android.content.Intent.EXTRA_TASK_ID; import static android.content.Intent.EXTRA_USER; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; @@ -63,7 +63,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 20d8054f6e90..67f9062b0a66 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -18,7 +18,7 @@ package com.android.wm.shell.draganddrop; import static android.app.StatusBarManager.DISABLE_NONE; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 0fbdf90fd9d5..8467cc5fc591 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -120,6 +120,11 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); + if (mTargetViewContainer != null) { + // init can be called multiple times, remove the old one from view hierarchy first. + mWindowManager.removeViewImmediate(mTargetViewContainer); + } + mTargetView = new DismissCircleView(mContext); mTargetViewContainer = new FrameLayout(mContext); mTargetViewContainer.setBackgroundDrawable( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 82e827398bb7..da4bbe81a3e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -189,7 +189,7 @@ public class PipMenuView extends FrameLayout { mEnterSplitButton = findViewById(R.id.enter_split); mEnterSplitButton.setAlpha(0); mEnterSplitButton.setOnClickListener(v -> { - if (mMenuContainer.getAlpha() != 0) { + if (mEnterSplitButton.getAlpha() != 0) { enterSplit(); } }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 02edaa00fac3..a91dfe1c13e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -20,7 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import java.util.concurrent.Executor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 0261df4b291b..05552aad2303 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -22,8 +22,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -66,9 +66,10 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; @@ -197,7 +198,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, new WindowContainerTransaction()); } - private boolean moveToStage(int taskId, @SplitScreen.StageType int stageType, + private boolean moveToStage(int taskId, @StageType int stageType, @SplitPosition int stagePosition, WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); if (task == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java index e320c2a23a0a..3e7a1004ed7a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java @@ -26,8 +26,8 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; @@ -43,7 +43,7 @@ import android.util.Slog; import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.util.FrameworkStatsLog; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 8ce58ae333f8..e30e6c537c93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -26,9 +26,9 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -89,10 +89,11 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitWindowManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.StagedSplitBounds; @@ -157,11 +158,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mKeyguardOccluded; private boolean mDeviceSleep; - @SplitScreen.StageType + @StageType private int mDismissTop = NO_DISMISS; /** The target stage to dismiss to when unlock after folded. */ - @SplitScreen.StageType + @StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; private final Runnable mOnTransitionAnimationComplete = () -> { @@ -274,7 +275,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mSideStageListener.mVisible && mMainStageListener.mVisible; } - @SplitScreen.StageType + @StageType int getStageOfTask(int taskId) { if (mMainStage.containsTask(taskId)) { return STAGE_TYPE_MAIN; @@ -285,7 +286,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return STAGE_TYPE_UNDEFINED; } - boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitScreen.StageType int stageType, + boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType, @SplitPosition int stagePosition, WindowContainerTransaction wct) { StageTaskListener targetStage; int sideStagePosition; @@ -435,7 +436,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } public void startIntent(PendingIntent intent, Intent fillInIntent, - @SplitScreen.StageType int stage, @SplitPosition int position, + @StageType int stage, @SplitPosition int position, @androidx.annotation.Nullable Bundle options, @Nullable RemoteTransition remoteTransition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -457,7 +458,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - Bundle resolveStartStage(@SplitScreen.StageType int stage, + Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, @androidx.annotation.Nullable Bundle options, @androidx.annotation.Nullable WindowContainerTransaction wct) { switch (stage) { @@ -510,12 +511,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return options; } - @SplitLayout.SplitPosition + @SplitPosition int getSideStagePosition() { return mSideStagePosition; } - @SplitLayout.SplitPosition + @SplitPosition int getMainStagePosition() { return SplitLayout.reversePosition(mSideStagePosition); } @@ -670,7 +671,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, * an existing WindowContainerTransaction (rather than applying immediately). This is intended * to be used when exiting split might be bundled with other window operations. */ - void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop, + void prepareExitSplitScreen(@StageType int stageToTop, @NonNull WindowContainerTransaction wct) { mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); @@ -1086,7 +1087,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return null; } - @SplitScreen.StageType + @StageType private int getStageType(StageTaskListener stage) { return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; } @@ -1213,7 +1214,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final TransitionInfo.Change change = info.getChanges().get(iC); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo == null || !taskInfo.hasParentTask()) continue; - final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo)); + final @StageType int stageType = getStageType(getStageOfTask(taskInfo)); if (stageType == STAGE_TYPE_MAIN) { mainChild = change; } else if (stageType == STAGE_TYPE_SIDE) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 5b0824567194..cd10b9fde444 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SurfaceUtils; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitDecorManager; +import com.android.wm.shell.splitscreen.SplitScreen.StageType; import java.io.PrintWriter; @@ -321,7 +322,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, - @SplitScreen.StageType int stage) { + @StageType int stage) { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { int taskId = mChildrenTaskInfo.keyAt(i); listener.onTaskStageChanged(taskId, stage, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java index aec81a1ee86a..c5d231262cd2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java @@ -20,7 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import java.util.concurrent.Executor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java index ad2b988a67ff..f1520edf53b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java @@ -20,8 +20,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import android.app.ActivityManager; @@ -62,7 +62,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java index aab7902232bf..e1850396a5c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java @@ -17,13 +17,13 @@ package com.android.wm.shell.stagesplit; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.util.FrameworkStatsLog; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; /** * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java index 47eb271800c3..a17942ff7cff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java @@ -31,9 +31,9 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -87,7 +87,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; -import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitWindowManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.Transitions; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 7fbffc9b4853..fe66e225ad4a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -24,9 +24,9 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 8128bbe3709e..85f6789c3435 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -19,9 +19,9 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index d17c32817994..13aff3812767 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -384,7 +384,16 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName( return base::unexpected(IOError::PAGES_MISSING); } - auto offset = dtohl(entry_offset_ptr.value()); + uint32_t offset; + uint16_t res_idx; + if (type->flags & ResTable_type::FLAG_SPARSE) { + auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>(); + offset = dtohs(sparse_entry->offset) * 4u; + res_idx = dtohs(sparse_entry->idx); + } else { + offset = dtohl(entry_offset_ptr.value()); + res_idx = entry_idx; + } if (offset != ResTable_type::NO_ENTRY) { auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>(); if (!entry) { @@ -394,7 +403,7 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName( if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) { // The package ID will be overridden by the caller (due to runtime assignment of package // IDs for shared libraries). - return make_resid(0x00, *type_idx + type_id_offset_ + 1, entry_idx); + return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx); } } } @@ -686,6 +695,12 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, std::unordered_set<uint32_t> finalized_ids; const auto lib_alias = child_chunk.header<ResTable_staged_alias_header>(); if (!lib_alias) { + LOG(ERROR) << "RES_TABLE_STAGED_ALIAS_TYPE is too small."; + return {}; + } + if ((child_chunk.data_size() / sizeof(ResTable_staged_alias_entry)) + < dtohl(lib_alias->count)) { + LOG(ERROR) << "RES_TABLE_STAGED_ALIAS_TYPE is too small to hold entries."; return {}; } const auto entry_begin = child_chunk.data_ptr().convert<ResTable_staged_alias_entry>(); diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index f356c8130080..d214e2dfef7b 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -95,6 +95,38 @@ TEST(LoadedArscTest, LoadSparseEntryApp) { ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); } +TEST(LoadedArscTest, FindSparseEntryApp) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc", + &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), + contents.length()); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26)); + ASSERT_THAT(package, NotNull()); + + const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1; + const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); + + // Ensure that AAPT2 sparsely encoded the v26 config as expected. + auto type_entry = std::find_if( + type_spec->type_entries.begin(), type_spec->type_entries.end(), + [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; }); + ASSERT_NE(type_entry, type_spec->type_entries.end()); + ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0); + + // Test fetching a resource with only sparsely encoded configs by name. + auto id = package->FindEntryByName(u"string", u"only_v26"); + ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0)); +} + TEST(LoadedArscTest, LoadSharedLibrary) { std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc", diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h index 243e74fac65a..2492dbf33f4a 100644 --- a/libs/androidfw/tests/data/sparse/R.h +++ b/libs/androidfw/tests/data/sparse/R.h @@ -27,21 +27,22 @@ struct R { struct integer { enum : uint32_t { foo_0 = 0x7f010000, - foo_1 = 0x7f010000, - foo_2 = 0x7f010000, - foo_3 = 0x7f010000, - foo_4 = 0x7f010000, - foo_5 = 0x7f010000, - foo_6 = 0x7f010000, - foo_7 = 0x7f010000, - foo_8 = 0x7f010000, - foo_9 = 0x7f010000, + foo_1 = 0x7f010001, + foo_2 = 0x7f010002, + foo_3 = 0x7f010003, + foo_4 = 0x7f010004, + foo_5 = 0x7f010005, + foo_6 = 0x7f010006, + foo_7 = 0x7f010007, + foo_8 = 0x7f010008, + foo_9 = 0x7f010009, }; }; struct string { enum : uint32_t { foo_999 = 0x7f0203e7, + only_v26 = 0x7f0203e8 }; }; }; diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh index e7e1d603ea4e..4ea5468c7df9 100755 --- a/libs/androidfw/tests/data/sparse/gen_strings.sh +++ b/libs/androidfw/tests/data/sparse/gen_strings.sh @@ -14,5 +14,7 @@ do fi done echo "</resources>" >> $OUTPUT_default + +echo " <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26 echo "</resources>" >> $OUTPUT_v26 diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk Binary files differindex 599a370dbfb1..b08a621195c0 100644 --- a/libs/androidfw/tests/data/sparse/not_sparse.apk +++ b/libs/androidfw/tests/data/sparse/not_sparse.apk diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml index b6f82997d18b..d116087ec3c0 100644 --- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml +++ b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml @@ -333,4 +333,5 @@ <string name="foo_993">9930</string> <string name="foo_996">9960</string> <string name="foo_999">9990</string> + <string name="only_v26">only v26</string> </resources> diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk Binary files differindex 1f9bba31b0a1..9fd01fbf2941 100644 --- a/libs/androidfw/tests/data/sparse/sparse.apk +++ b/libs/androidfw/tests/data/sparse/sparse.apk diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml index 2272a375fb83..2624a419e4a4 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml @@ -21,10 +21,10 @@ <dimen name="settingslib_switchbar_margin">16dp</dimen> <!-- Size of layout margin left --> - <dimen name="settingslib_switchbar_padding_left">24dp</dimen> + <dimen name="settingslib_switchbar_padding_left">20dp</dimen> <!-- Size of layout margin right --> - <dimen name="settingslib_switchbar_padding_right">16dp</dimen> + <dimen name="settingslib_switchbar_padding_right">20dp</dimen> <!-- Minimum width of switch --> <dimen name="settingslib_min_switch_width">52dp</dimen> diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml index 6362882e2332..157a54e3573d 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml @@ -24,7 +24,7 @@ <dimen name="settingslib_restricted_icon_margin_end">16dp</dimen> <!-- Size of title margin --> - <dimen name="settingslib_switch_title_margin">16dp</dimen> + <dimen name="settingslib_switch_title_margin">24dp</dimen> <!-- SwitchBar sub settings margin start / end --> <dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen> diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index bf0dc7bce5f9..1343895ed93d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -261,8 +261,6 @@ public class WifiStatusTracker { private void updateWifiState() { state = mWifiManager.getWifiState(); enabled = state == WifiManager.WIFI_STATE_ENABLED; - isCarrierMerged = false; - subId = 0; } private void updateRssi(int newRssi) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index db301f698753..aca4fe456195 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -25,6 +25,7 @@ import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; import static android.provider.Settings.SET_ALL_RESULT_DISABLED; import static android.provider.Settings.SET_ALL_RESULT_FAILURE; import static android.provider.Settings.SET_ALL_RESULT_SUCCESS; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER; import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; @@ -3585,7 +3586,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 204; + private static final int SETTINGS_VERSION = 205; private final int mUserId; @@ -5227,6 +5228,30 @@ public class SettingsProvider extends ContentProvider { currentVersion = 204; } + if (currentVersion == 204) { + // Version 204: Reset the + // Secure#ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT as enabled + // status for showing the tooltips. + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting accessibilityButtonMode = secureSettings.getSettingLocked( + Secure.ACCESSIBILITY_BUTTON_MODE); + if (!accessibilityButtonMode.isNull() + && accessibilityButtonMode.getValue().equals( + String.valueOf(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU))) { + if (isGestureNavigateEnabled() + && hasValueInA11yButtonTargets(secureSettings)) { + secureSettings.insertSettingLocked( + Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, + /* enabled */ "1", + /* tag= */ null, + /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + + currentVersion = 205; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml deleted file mode 100644 index 620dd4891b9b..000000000000 --- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:pathData="M 0, 0 C 0.1217, 0.0462, 0.15, 0.4686, 0.1667, 0.66 C 0.1834, 0.8878, 0.1667, 1, 1, 1" />
\ No newline at end of file diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml deleted file mode 100644 index a268abce0c27..000000000000 --- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1" />
\ No newline at end of file diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 702060338359..a0d335db92d6 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -21,6 +21,7 @@ import android.app.ActivityTaskManager import android.app.PendingIntent import android.app.TaskInfo import android.graphics.Matrix +import android.graphics.Path import android.graphics.Rect import android.graphics.RectF import android.os.Looper @@ -34,6 +35,7 @@ import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.animation.Interpolator import android.view.animation.PathInterpolator import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils @@ -45,16 +47,46 @@ private const val TAG = "ActivityLaunchAnimator" * A class that allows activities to be started in a seamless way from a view that is transforming * nicely into the starting window. */ -class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { +class ActivityLaunchAnimator( + private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS) +) { companion object { + @JvmField + val TIMINGS = LaunchAnimator.Timings( + totalDuration = 500L, + contentBeforeFadeOutDelay = 0L, + contentBeforeFadeOutDuration = 150L, + contentAfterFadeInDelay = 150L, + contentAfterFadeInDuration = 183L + ) + + val INTERPOLATORS = LaunchAnimator.Interpolators( + positionInterpolator = Interpolators.EMPHASIZED, + positionXInterpolator = createPositionXInterpolator(), + contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, + contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) + ) + + /** Durations & interpolators for the navigation bar fading in & out. */ private const val ANIMATION_DURATION_NAV_FADE_IN = 266L private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L - private const val ANIMATION_DELAY_NAV_FADE_IN = - LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN - private const val LAUNCH_TIMEOUT = 1000L + private val ANIMATION_DELAY_NAV_FADE_IN = + TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN - private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f) + private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) + + /** The time we wait before timing out the remote animation after starting the intent. */ + private const val LAUNCH_TIMEOUT = 1000L + + private fun createPositionXInterpolator(): Interpolator { + val path = Path().apply { + moveTo(0f, 0f) + cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f) + cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f) + } + return PathInterpolator(path) + } } /** @@ -107,8 +139,8 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { val animationAdapter = if (!hideKeyguardWithAnimation) { RemoteAnimationAdapter( runner, - LaunchAnimator.ANIMATION_DURATION, - LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */ + TIMINGS.totalDuration, + TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */ ) } else { null @@ -436,7 +468,6 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { .withAlpha(1f) .withMatrix(matrix) .withWindowCrop(windowCrop) - .withLayer(window.prefixOrderIndex) .withCornerRadius(cornerRadius) .withVisibility(true) .build() @@ -449,7 +480,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { state: LaunchAnimator.State, linearProgress: Float ) { - val fadeInProgress = LaunchAnimator.getProgress(linearProgress, + val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT) val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) @@ -464,7 +495,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { .withWindowCrop(windowCrop) .withVisibility(true) } else { - val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0, + val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0, ANIMATION_DURATION_NAV_FADE_OUT) params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index de82ebdc6b1c..066e169dcde3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -20,7 +20,6 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.app.Dialog -import android.content.Context import android.graphics.Color import android.graphics.Rect import android.os.Looper @@ -28,10 +27,11 @@ import android.service.dreams.IDreamManager import android.util.Log import android.util.MathUtils import android.view.GhostView +import android.view.SurfaceControl import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewTreeObserver.OnPreDrawListener +import android.view.ViewRootImpl import android.view.WindowManager import android.widget.FrameLayout import kotlin.math.roundToInt @@ -42,12 +42,20 @@ private const val TAG = "DialogLaunchAnimator" * A class that allows dialogs to be started in a seamless way from a view that is transforming * nicely into the starting dialog. */ -class DialogLaunchAnimator( - private val context: Context, - private val launchAnimator: LaunchAnimator, - private val dreamManager: IDreamManager +class DialogLaunchAnimator @JvmOverloads constructor( + private val dreamManager: IDreamManager, + private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), + private var isForTesting: Boolean = false ) { private companion object { + private val TIMINGS = ActivityLaunchAnimator.TIMINGS + + // We use the same interpolator for X and Y axis to make sure the dialog does not move out + // of the screen bounds during the animation. + private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy( + positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator + ) + private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running } @@ -96,14 +104,14 @@ class DialogLaunchAnimator( animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) val animatedDialog = AnimatedDialog( - context, launchAnimator, dreamManager, animateFrom, onDialogDismissed = { openedDialogs.remove(it) }, dialog = dialog, animateBackgroundBoundsChange, - animatedParent + animatedParent, + isForTesting ) openedDialogs.add(animatedDialog) @@ -157,7 +165,6 @@ class DialogLaunchAnimator( } private class AnimatedDialog( - private val context: Context, private val launchAnimator: LaunchAnimator, private val dreamManager: IDreamManager, @@ -174,10 +181,16 @@ private class AnimatedDialog( val dialog: Dialog, /** Whether we should animate the dialog background when its bounds change. */ - private val animateBackgroundBoundsChange: Boolean, + animateBackgroundBoundsChange: Boolean, /** Launch animation corresponding to the parent [AnimatedDialog]. */ - private val parentAnimatedDialog: AnimatedDialog? = null + private val parentAnimatedDialog: AnimatedDialog? = null, + + /** + * Whether we are currently running in a test, in which case we need to disable + * synchronization. + */ + private val isForTesting: Boolean ) { /** * The DecorView of this dialog window. @@ -266,14 +279,14 @@ private class AnimatedDialog( // and the view that we added so that we can dismiss the dialog when this view is // clicked. This is necessary because DecorView overrides onTouchEvent and therefore we // can't set the click listener directly on the (now fullscreen) DecorView. - val fullscreenTransparentBackground = FrameLayout(context) + val fullscreenTransparentBackground = FrameLayout(dialog.context) decorView.addView( fullscreenTransparentBackground, 0 /* index */, FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) ) - val dialogContentWithBackground = FrameLayout(context) + val dialogContentWithBackground = FrameLayout(dialog.context) dialogContentWithBackground.background = decorView.background // Make the window background transparent. Note that setting the window (or DecorView) @@ -365,59 +378,77 @@ private class AnimatedDialog( // Show the dialog. dialog.show() - // Add a temporary touch surface ghost as soon as the window is ready to draw. This - // temporary ghost will be drawn together with the touch surface, but in the dialog - // window. Once it is drawn, we will make the touch surface invisible, and then start the - // animation. We do all this synchronization to avoid flicker that would occur if we made - // the touch surface invisible too early (before its ghost is drawn), leading to one or more - // frames with a hole instead of the touch surface (or its ghost). - decorView.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - decorView.viewTreeObserver.removeOnPreDrawListener(this) - addTemporaryTouchSurfaceGhost() - return true - } - }) - decorView.invalidate() + addTouchSurfaceGhost() } - private fun addTemporaryTouchSurfaceGhost() { + private fun addTouchSurfaceGhost() { + if (decorView.viewRootImpl == null) { + // Make sure that we have access to the dialog view root to synchronize the creation of + // the ghost. + decorView.post(::addTouchSurfaceGhost) + return + } + // Create a ghost of the touch surface (which will make the touch surface invisible) and add - // it to the dialog. We will wait for this ghost to be drawn before starting the animation. - val ghost = GhostView.addGhost(touchSurface, decorView) - - // The ghost of the touch surface was just created, so the touch surface was made invisible. - // We make it visible again until the ghost is actually drawn. - touchSurface.visibility = View.VISIBLE - - // Wait for the ghost to be drawn before continuing. - ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - ghost.viewTreeObserver.removeOnPreDrawListener(this) - onTouchSurfaceGhostDrawn() - return true - } + // it to the host dialog. We trigger a one off synchronization to make sure that this is + // done in sync between the two different windows. + synchronizeNextDraw(then = { + isTouchSurfaceGhostDrawn = true + maybeStartLaunchAnimation() }) - ghost.invalidate() - } + GhostView.addGhost(touchSurface, decorView) - private fun onTouchSurfaceGhostDrawn() { - // Make the touch surface invisible and make sure that it stays invisible as long as the - // dialog is shown or animating. - touchSurface.visibility = View.INVISIBLE + // The ghost of the touch surface was just created, so the touch surface is currently + // invisible. We need to make sure that it stays invisible as long as the dialog is shown or + // animating. (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true) + } - // Add a pre draw listener to (maybe) start the animation once the touch surface is - // actually invisible. - touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - touchSurface.viewTreeObserver.removeOnPreDrawListener(this) - isTouchSurfaceGhostDrawn = true - maybeStartLaunchAnimation() - return true + /** + * Synchronize the next draw of the touch surface and dialog view roots so that they are + * performed at the same time, in the same transaction. This is necessary to make sure that the + * ghost of the touch surface is drawn at the same time as the touch surface is made invisible + * (or inversely, removed from the UI when the touch surface is made visible). + */ + private fun synchronizeNextDraw(then: () -> Unit) { + if (isForTesting || !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null || + !decorView.isAttachedToWindow || decorView.viewRootImpl == null) { + // No need to synchronize if either the touch surface or dialog view is not attached + // to a window. + then() + return + } + + // Consume the next frames of both view roots to make sure the ghost view is drawn at + // exactly the same time as when the touch surface is made invisible. + var remainingTransactions = 0 + val mergedTransactions = SurfaceControl.Transaction() + + fun onTransaction(transaction: SurfaceControl.Transaction?) { + remainingTransactions-- + transaction?.let { mergedTransactions.merge(it) } + + if (remainingTransactions == 0) { + mergedTransactions.apply() + then() } - }) - touchSurface.invalidate() + } + + fun consumeNextDraw(viewRootImpl: ViewRootImpl) { + if (viewRootImpl.consumeNextDraw(::onTransaction)) { + remainingTransactions++ + + // Make sure we trigger a traversal. + viewRootImpl.view.invalidate() + } + } + + consumeNextDraw(touchSurface.viewRootImpl) + consumeNextDraw(decorView.viewRootImpl) + + if (remainingTransactions == 0) { + then() + } } private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { @@ -483,7 +514,7 @@ private class AnimatedDialog( private fun onDialogDismissed() { if (Looper.myLooper() != Looper.getMainLooper()) { - context.mainExecutor.execute { onDialogDismissed() } + dialog.context.mainExecutor.execute { onDialogDismissed() } return } @@ -556,25 +587,12 @@ private class AnimatedDialog( .removeOnLayoutChangeListener(backgroundLayoutListener) } - // The animated ghost was just removed. We create a temporary ghost that will be - // removed only once we draw the touch surface, to avoid flickering that would - // happen when removing the ghost too early (before the touch surface is drawn). - GhostView.addGhost(touchSurface, decorView) - - touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - touchSurface.viewTreeObserver.removeOnPreDrawListener(this) - - // Now that the touch surface was drawn, we can remove the temporary ghost - // and instantly dismiss the dialog. - GhostView.removeGhost(touchSurface) - onAnimationFinished(true /* instantDismiss */) - onDialogDismissed(this@AnimatedDialog) - - return true - } + // Make sure that the removal of the ghost and making the touch surface visible is + // done at the same time. + synchronizeNextDraw(then = { + onAnimationFinished(true /* instantDismiss */) + onDialogDismissed(this@AnimatedDialog) }) - touchSurface.invalidate() } ) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt index 3bf6c5ebd091..ebe96ebf2988 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt @@ -27,25 +27,19 @@ import android.util.Log import android.util.MathUtils import android.view.View import android.view.ViewGroup -import android.view.animation.AnimationUtils -import android.view.animation.PathInterpolator +import android.view.animation.Interpolator +import com.android.systemui.animation.Interpolators.LINEAR import kotlin.math.roundToInt private const val TAG = "LaunchAnimator" /** A base class to animate a window launch (activity or dialog) from a view . */ -class LaunchAnimator @JvmOverloads constructor( - context: Context, - private val isForTesting: Boolean = false +class LaunchAnimator( + private val timings: Timings, + private val interpolators: Interpolators ) { companion object { internal const val DEBUG = false - const val ANIMATION_DURATION = 500L - private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L - private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L - private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT - - private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f) private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) /** @@ -53,23 +47,20 @@ class LaunchAnimator @JvmOverloads constructor( * sub-animation starting [delay] ms after the launch animation and that lasts [duration]. */ @JvmStatic - fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float { + fun getProgress( + timings: Timings, + linearProgress: Float, + delay: Long, + duration: Long + ): Float { return MathUtils.constrain( - (linearProgress * ANIMATION_DURATION - delay) / duration, + (linearProgress * timings.totalDuration - delay) / duration, 0.0f, 1.0f ) } } - /** The interpolator used for the width, height, Y position and corner radius. */ - private val animationInterpolator = AnimationUtils.loadInterpolator(context, - R.interpolator.launch_animation_interpolator_y) - - /** The interpolator used for the X position. */ - private val animationInterpolatorX = AnimationUtils.loadInterpolator(context, - R.interpolator.launch_animation_interpolator_x) - private val launchContainerLocation = IntArray(2) private val cornerRadii = FloatArray(8) @@ -159,6 +150,45 @@ class LaunchAnimator @JvmOverloads constructor( fun cancel() } + /** The timings (durations and delays) used by this animator. */ + class Timings( + /** The total duration of the animation. */ + val totalDuration: Long, + + /** The time to wait before fading out the expanding content. */ + val contentBeforeFadeOutDelay: Long, + + /** The duration of the expanding content fade out. */ + val contentBeforeFadeOutDuration: Long, + + /** + * The time to wait before fading in the expanded content (usually an activity or dialog + * window). + */ + val contentAfterFadeInDelay: Long, + + /** The duration of the expanded content fade in. */ + val contentAfterFadeInDuration: Long + ) + + /** The interpolators used by this animator. */ + data class Interpolators( + /** The interpolator used for the Y position, width, height and corner radius. */ + val positionInterpolator: Interpolator, + + /** + * The interpolator used for the X position. This can be different than + * [positionInterpolator] to create an arc-path during the animation. + */ + val positionXInterpolator: Interpolator = positionInterpolator, + + /** The interpolator used when fading out the expanding content. */ + val contentBeforeFadeOutInterpolator: Interpolator, + + /** The interpolator used when fading in the expanded content. */ + val contentAfterFadeInInterpolator: Interpolator + ) + /** * Start a launch animation controlled by [controller] towards [endState]. An intermediary * layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and @@ -221,8 +251,8 @@ class LaunchAnimator @JvmOverloads constructor( // Update state. val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = if (isForTesting) 0 else ANIMATION_DURATION - animator.interpolator = Interpolators.LINEAR + animator.duration = timings.totalDuration + animator.interpolator = LINEAR val launchContainerOverlay = launchContainer.overlay var cancelled = false @@ -260,8 +290,8 @@ class LaunchAnimator @JvmOverloads constructor( // TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non // reversed animation. val linearProgress = animation.animatedFraction - val progress = animationInterpolator.getInterpolation(linearProgress) - val xProgress = animationInterpolatorX.getInterpolation(linearProgress) + val progress = interpolators.positionInterpolator.getInterpolation(linearProgress) + val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress) val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress) val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f @@ -278,7 +308,12 @@ class LaunchAnimator @JvmOverloads constructor( // The expanding view can/should be hidden once it is completely covered by the opening // window. - state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1 + state.visible = getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) < 1 applyStateToWindowBackgroundLayer( windowBackgroundLayer, @@ -337,14 +372,25 @@ class LaunchAnimator @JvmOverloads constructor( // We first fade in the background layer to hide the expanding view, then fade it out // with SRC mode to draw a hole punch in the status bar and reveal the opening window. - val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) + val fadeInProgress = getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) if (fadeInProgress < 1) { - val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress) + val alpha = + interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) drawable.alpha = (alpha * 0xFF).roundToInt() } else { val fadeOutProgress = getProgress( - linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW) - val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress) + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration + ) + val alpha = + 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress) drawable.alpha = (alpha * 0xFF).roundToInt() if (drawHole) { diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml index e9bd638dfbf4..e80cfafdd71a 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml @@ -25,6 +25,9 @@ <!-- Margin around the various security views --> <dimen name="keyguard_security_view_top_margin">12dp</dimen> + <!-- Padding for the lock icon on the keyguard --> + <dimen name="lock_icon_padding">16dp</dimen> + <!-- Overload default clock widget parameters --> <dimen name="widget_big_font_size">100dp</dimen> <dimen name="widget_label_font_size">18sp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 4fc38a813e8a..16010430df11 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -219,6 +219,9 @@ <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] --> <string name="kg_face_not_recognized">Not recognized</string> + <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] --> + <string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string> + <!-- Instructions telling the user remaining times when enter SIM PIN view. --> <plurals name="kg_password_default_pin_message"> <item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml index 363a022efdac..eb08434cbc54 100644 --- a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml +++ b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml @@ -13,17 +13,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" +<inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <stroke - android:color="?androidprv:attr/colorAccentPrimaryVariant" - android:width="1dp"/> - <corners android:radius="20dp"/> - <padding - android:left="16dp" - android:right="16dp" - android:top="8dp" - android:bottom="8dp" /> - <solid android:color="@android:color/transparent" /> -</shape> + android:insetBottom="6dp" + android:insetTop="6dp"> + <shape android:shape="rectangle"> + <stroke + android:color="?androidprv:attr/colorAccentPrimaryVariant" + android:width="1dp"/> + <corners android:radius="20dp"/> + <padding + android:left="16dp" + android:right="16dp" + android:top="8dp" + android:bottom="8dp"/> + <solid android:color="@android:color/transparent"/> + </shape> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml index 1e0ce0026d8e..46cbc253175e 100644 --- a/packages/SystemUI/res/layout/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -18,64 +18,71 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" - android:gravity="center_horizontal" - android:elevation="@dimen/biometric_dialog_elevation"> + android:elevation="@dimen/biometric_dialog_elevation" + android:orientation="vertical"> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> - <ImageView - android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + <LinearLayout + android:id="@+id/auth_credential_header" + style="@style/AuthCredentialHeaderStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true"> - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Title"/> + <ImageView + android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:contentDescription="@null" /> - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Subtitle"/> + <TextView + android:id="@+id/title" + style="@style/TextAppearance.AuthCredential.Title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <TextView - android:id="@+id/description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Description"/> + <TextView + android:id="@+id/subtitle" + style="@style/TextAppearance.AuthCredential.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + <TextView + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <ImeAwareEditText - android:id="@+id/lockPassword" - android:layout_width="208dp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:minHeight="48dp" - android:gravity="center" - android:inputType="textPassword" - android:maxLength="500" - android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" - style="@style/TextAppearance.AuthCredential.PasswordEntry"/> + </LinearLayout> - <TextView - android:id="@+id/error" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Error"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical" + android:layout_alignParentBottom="true"> + + <ImeAwareEditText + android:id="@+id/lockPassword" + style="@style/TextAppearance.AuthCredential.PasswordEntry" + android:layout_width="208dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" + android:inputType="textPassword" + android:minHeight="48dp" /> + + <TextView + android:id="@+id/error" + style="@style/TextAppearance.AuthCredential.Error" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="5"/> + </RelativeLayout> </com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml index 4939ea2c99ee..470298ea0b13 100644 --- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml @@ -22,76 +22,81 @@ android:gravity="center_horizontal" android:elevation="@dimen/biometric_dialog_elevation"> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> - - <ImageView - android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> - - <TextView - android:id="@+id/title" + <RelativeLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Title"/> + android:layout_height="match_parent" + android:orientation="vertical"> - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Subtitle"/> + <LinearLayout + android:id="@+id/auth_credential_header" + style="@style/AuthCredentialHeaderStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content"> - <TextView - android:id="@+id/description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Description"/> + <ImageView + android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:contentDescription="@null" /> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + <TextView + android:id="@+id/title" + style="@style/TextAppearance.AuthCredential.Title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="center" - android:paddingLeft="0dp" - android:paddingRight="0dp" - android:paddingTop="0dp" - android:paddingBottom="16dp" - android:clipToPadding="false"> - - <FrameLayout - android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_weight="1" - style="@style/LockPatternContainerStyle"> - - <com.android.internal.widget.LockPatternView - android:id="@+id/lockPattern" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - style="@style/LockPatternStyleBiometricPrompt"/> + <TextView + android:id="@+id/subtitle" + style="@style/TextAppearance.AuthCredential.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/auth_credential_header" + android:gravity="center" + android:orientation="vertical" + android:paddingBottom="16dp" + android:paddingTop="60dp"> + + <FrameLayout + style="@style/LockPatternContainerStyle" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1"> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPattern" + style="@style/LockPatternStyle" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" /> - </FrameLayout> + </FrameLayout> - <TextView - android:id="@+id/error" + </LinearLayout> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Error"/> + android:layout_alignParentBottom="true"> + + <TextView + android:id="@+id/error" + style="@style/TextAppearance.AuthCredential.Error" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> - </LinearLayout> + </LinearLayout> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + </RelativeLayout> </com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index e90a6446c47b..c575855d9af8 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -67,7 +67,6 @@ <ProgressBar android:id="@+id/wifi_searching_progress" - android:indeterminate="true" android:layout_width="340dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index a64ef3ea1cc6..07fd1b0c001c 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -24,9 +24,9 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="96dp" + android:layout_height="wrap_content" android:gravity="start|center_vertical" - android:paddingStart="16dp" + android:paddingStart="24dp" android:orientation="horizontal"> <ImageView android:id="@+id/header_icon" @@ -36,7 +36,7 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:paddingStart="16dp" android:paddingTop="20dp" android:paddingBottom="24dp" @@ -59,7 +59,7 @@ android:gravity="center_vertical" android:ellipsize="end" android:maxLines="1" - android:textColor="?android:attr/textColorTertiary" + android:textColor="?android:attr/textColorSecondary" android:fontFamily="roboto-regular" android:textSize="16sp"/> </LinearLayout> @@ -90,7 +90,7 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginStart="24dp" - android:layout_marginBottom="18dp" + android:layout_marginBottom="24dp" android:layout_marginEnd="24dp" android:orientation="horizontal"> @@ -98,7 +98,7 @@ android:id="@+id/stop" style="@style/MediaOutputRoundedOutlinedButton" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:minWidth="0dp" android:text="@string/keyboard_key_media_stop" android:visibility="gone"/> @@ -112,7 +112,7 @@ android:id="@+id/done" style="@style/MediaOutputRoundedOutlinedButton" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:minWidth="0dp" android:text="@string/inline_done_button"/> </LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index 2ac03c262edb..f5c6036a5a86 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -56,17 +56,4 @@ layout="@layout/qs_customize_panel" android:visibility="gone" /> - <ImageView - android:id="@+id/qs_drag_handle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginTop="24dp" - android:elevation="4dp" - android:importantForAccessibility="no" - android:scaleType="center" - android:src="@drawable/ic_qs_drag_handle" - android:tint="@color/qs_detail_button_white" - tools:ignore="UseAppTint" /> - </com.android.systemui.qs.QSContainerImpl> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 362e18d785ac..dabc3108458f 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -18,11 +18,14 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">2</integer> + <!-- The maximum number of rows in the QSPanel --> + <integer name="quick_settings_max_rows">3</integer> + <!-- The maximum number of rows in the QuickQSPanel --> - <integer name="quick_qs_panel_max_rows">4</integer> + <integer name="quick_qs_panel_max_rows">3</integer> <!-- The maximum number of tiles in the QuickQSPanel --> - <integer name="quick_qs_panel_max_tiles">8</integer> + <integer name="quick_qs_panel_max_tiles">6</integer> <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml index e4573c651039..e0b161456aa2 100644 --- a/packages/SystemUI/res/values-sw720dp-land/config.xml +++ b/packages/SystemUI/res/values-sw720dp-land/config.xml @@ -18,11 +18,14 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">2</integer> + <!-- The maximum number of rows in the QSPanel --> + <integer name="quick_settings_max_rows">3</integer> + <!-- The maximum number of rows in the QuickQSPanel --> - <integer name="quick_qs_panel_max_rows">4</integer> + <integer name="quick_qs_panel_max_rows">3</integer> <!-- The maximum number of tiles in the QuickQSPanel --> - <integer name="quick_qs_panel_max_tiles">8</integer> + <integer name="quick_qs_panel_max_tiles">6</integer> <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4dc52e7540be..e1afd3fe6a30 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -630,7 +630,7 @@ <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] --> - <string name="quick_settings_inversion_label">Invert colors</string> + <string name="quick_settings_inversion_label">Color inversion</string> <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] --> <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] --> <string name="quick_settings_more_settings">More settings</string> @@ -2037,8 +2037,8 @@ <string name="magnification_mode_switch_click_label">Switch</string> <!-- Accessibility floating menu strings --> - <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] --> - <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string> + <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] --> + <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string> <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] --> <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string> <!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 92985ded8a91..0cfd8d6d0716 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -200,9 +200,9 @@ <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/> - <style name="TextAppearance.AuthCredential"> + <style name="TextAppearance.AuthCredential" + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:accessibilityLiveRegion">polite</item> - <item name="android:gravity">center_horizontal</item> <item name="android:textAlignment">gravity</item> <item name="android:layout_gravity">top</item> <item name="android:textColor">?android:attr/textColorPrimary</item> @@ -210,44 +210,57 @@ <style name="TextAppearance.AuthCredential.Title"> <item name="android:fontFamily">google-sans</item> - <item name="android:paddingTop">12dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">24sp</item> + <item name="android:layout_marginTop">20dp</item> + <item name="android:textSize">36sp</item> </style> <style name="TextAppearance.AuthCredential.Subtitle"> <item name="android:fontFamily">google-sans</item> - <item name="android:paddingTop">8dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">16sp</item> + <item name="android:layout_marginTop">20dp</item> + <item name="android:textSize">18sp</item> </style> <style name="TextAppearance.AuthCredential.Description"> <item name="android:fontFamily">google-sans</item> - <item name="android:paddingTop">8dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">14sp</item> + <item name="android:layout_marginTop">20dp</item> + <item name="android:textSize">16sp</item> </style> <style name="TextAppearance.AuthCredential.Error"> <item name="android:paddingTop">6dp</item> + <item name="android:paddingBottom">18dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">14sp</item> <item name="android:textColor">?android:attr/colorError</item> + <item name="android:gravity">center</item> </style> - <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault"> + <style name="TextAppearance.AuthCredential.PasswordEntry"> <item name="android:gravity">center</item> <item name="android:singleLine">true</item> <item name="android:textColor">?android:attr/colorForeground</item> <item name="android:textSize">24sp</item> </style> + <style name="AuthCredentialHeaderStyle"> + <item name="android:paddingStart">48dp</item> + <item name="android:paddingEnd">24dp</item> + <item name="android:paddingTop">28dp</item> + <item name="android:paddingBottom">20dp</item> + <item name="android:orientation">vertical</item> + <item name="android:layout_gravity">top</item> + </style> + <style name="DeviceManagementDialogTitle"> <item name="android:gravity">center</item> <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item> </style> + <style name="AuthCredentialPasswordTheme" parent="@style/Theme.MaterialComponents.DayNight"> + <item name="colorPrimary">?android:attr/colorPrimary</item> + <item name="colorPrimaryDark">?android:attr/colorPrimary</item> + </style> + <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/> <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> @@ -307,9 +320,8 @@ <item name="android:maxWidth">420dp</item> <item name="android:minHeight">0dp</item> <item name="android:minWidth">0dp</item> - <item name="android:paddingBottom">0dp</item> - <item name="android:paddingHorizontal">44dp</item> - <item name="android:paddingTop">0dp</item> + <item name="android:paddingHorizontal">60dp</item> + <item name="android:paddingBottom">40dp</item> </style> <style name="LockPatternStyle"> diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index b6be6edc7a10..e46b6f12e4a3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -26,6 +26,7 @@ import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider +import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider import com.android.systemui.unfold.updates.DeviceFoldStateProvider import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider @@ -62,7 +63,7 @@ fun createUnfoldTransitionProgressProvider( mainExecutor ) - return if (config.isHingeAngleEnabled) { + val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) { PhysicsBasedUnfoldTransitionProgressProvider( mainHandler, foldStateProvider @@ -70,6 +71,10 @@ fun createUnfoldTransitionProgressProvider( } else { FixedTimingTransitionProgressProvider(foldStateProvider) } + return ScaleAwareTransitionProgressProvider( + unfoldTransitionProgressProvider, + context.contentResolver + ) } fun createConfig(context: Context): UnfoldTransitionConfig = diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt index e072d41e4eee..58d7dfb133a5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt @@ -2,7 +2,6 @@ package com.android.systemui.unfold.util import android.content.Context import android.os.RemoteException -import android.util.Log import android.view.IRotationWatcher import android.view.IWindowManager import android.view.Surface diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt new file mode 100644 index 000000000000..df9078a15520 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt @@ -0,0 +1,50 @@ +package com.android.systemui.unfold.util + +import android.animation.ValueAnimator +import android.content.ContentResolver +import android.database.ContentObserver +import android.provider.Settings +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener + +/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */ +class ScaleAwareTransitionProgressProvider( + unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + private val contentResolver: ContentResolver +) : UnfoldTransitionProgressProvider { + + private val scopedUnfoldTransitionProgressProvider = + ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) + + private val animatorDurationScaleObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + onAnimatorScaleChanged() + } + } + + init { + contentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), + /* notifyForDescendants= */ false, + animatorDurationScaleObserver) + onAnimatorScaleChanged() + } + + private fun onAnimatorScaleChanged() { + val animationsEnabled = ValueAnimator.areAnimatorsEnabled() + scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled) + } + + override fun addCallback(listener: TransitionProgressListener) { + scopedUnfoldTransitionProgressProvider.addCallback(listener) + } + + override fun removeCallback(listener: TransitionProgressListener) { + scopedUnfoldTransitionProgressProvider.removeCallback(listener) + } + + override fun destroy() { + contentResolver.unregisterContentObserver(animatorDurationScaleObserver) + scopedUnfoldTransitionProgressProvider.destroy() + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index ba6771644db1..237ca71027b5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -51,6 +51,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -335,6 +336,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; + private SensorPrivacyManager mSensorPrivacyManager; + private int mFaceAuthUserId; /** * Short delay before restarting fingerprint authentication after a successful try. This should @@ -1016,6 +1019,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Error is always the end of authentication lifecycle mFaceCancelSignal = null; + boolean cameraPrivacyEnabled = false; + if (mSensorPrivacyManager != null) { + cameraPrivacyEnabled = mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + mFaceAuthUserId); + } if (msgId == FaceManager.FACE_ERROR_CANCELED && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { @@ -1025,7 +1034,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab setFaceRunningState(BIOMETRIC_STATE_STOPPED); } - if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE + final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE; + + if (isHwUnavailable || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) { if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) { mHardwareFaceUnavailableRetryCount++; @@ -1041,6 +1052,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab requireStrongAuthIfAllLockedOut(); } + if (isHwUnavailable && cameraPrivacyEnabled) { + errString = mContext.getString(R.string.kg_face_sensor_privacy_enabled); + } + for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1816,6 +1831,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils = lockPatternUtils; mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mHandler = new Handler(mainLooper) { @Override @@ -2517,6 +2533,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // This would need to be updated for multi-sensor devices final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() && mFaceSensorProperties.get(0).supportsFaceDetection; + mFaceAuthUserId = userId; if (isEncryptedOrLockdown(userId) && supportsFaceDetection) { mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId); } else { diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index b2ecc6140460..0c1934cb977b 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -218,11 +218,14 @@ public class LockIconView extends FrameLayout implements Dumpable { @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); - pw.println("Radius in pixels: " + mRadius); - pw.println("topLeft= (" + getX() + ", " + getY() + ")"); - pw.println("topLeft= (" + getX() + ", " + getY() + ")"); - pw.println("mIconType=" + typeToString(mIconType)); - pw.println("mAod=" + mAod); + pw.println("Lock Icon View Parameters:"); + pw.println(" Center in px (x, y)= (" + + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); + pw.println(" Radius in pixels: " + mRadius); + pw.println(" mIconType=" + typeToString(mIconType)); + pw.println(" mAod=" + mAod); + pw.println("Lock Icon View actual measurements:"); + pw.println(" topLeft= (" + getX() + ", " + getY() + ")"); + pw.println(" width=" + getWidth() + " height=" + getHeight()); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 163392314cfc..e267c5c7919c 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -35,13 +35,14 @@ import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.media.AudioAttributes; import android.os.Process; +import android.os.VibrationEffect; import android.os.Vibrator; import android.util.DisplayMetrics; +import android.util.Log; import android.util.MathUtils; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -83,6 +84,7 @@ import javax.inject.Inject; */ @StatusBarComponent.StatusBarScope public class LockIconViewController extends ViewController<LockIconView> implements Dumpable { + private static final String TAG = "LockIconViewController"; private static final float sDefaultDensity = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); @@ -91,6 +93,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); + private static final long LONG_PRESS_TIMEOUT = 200L; // milliseconds @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardViewController mKeyguardViewController; @@ -112,6 +115,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Nullable private final Vibrator mVibrator; @Nullable private final AuthRippleController mAuthRippleController; + // Tracks the velocity of a touch to help filter out the touches that move too fast. + private VelocityTracker mVelocityTracker; + // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. + private int mActivePointerId = -1; + private VibrationEffect mTick; + private boolean mIsDozing; private boolean mIsBouncerShowing; private boolean mRunningFPS; @@ -122,6 +131,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mUserUnlockedWithBiometric; private Runnable mCancelDelayedUpdateVisibilityRunnable; private Runnable mOnGestureDetectedRunnable; + private Runnable mLongPressCancelRunnable; private boolean mUdfpsSupported; private float mHeightPixels; @@ -181,7 +191,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setImageDrawable(mIcon); mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); mLockedLabel = resources.getString(R.string.accessibility_lock_icon); - dumpManager.registerDumpable("LockIconViewController", this); + dumpManager.registerDumpable(TAG, this); } @Override @@ -320,7 +330,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme getResources().getString(R.string.accessibility_enter_hint)); public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(v, info); - if (isClickable()) { + if (isActionable()) { if (mShowLockIcon) { info.addAction(mAccessibilityAuthenticateHint); } else if (mShowUnlockIcon) { @@ -425,10 +435,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mAodFp.setAlpha(255 * mInterpolatedDarkAmount); } - if (mShowAodLockIcon) { - mView.setTranslationX(offsetX); - mView.setTranslationY(offsetY); - } + mView.setTranslationX(offsetX); + mView.setTranslationY(offsetY); } private void updateIsUdfpsEnrolled() { @@ -477,7 +485,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void onKeyguardVisibilityChanged(boolean showing) { // reset mIsBouncerShowing state in case it was preemptively set - // onAffordanceClick + // onLongPress mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); updateVisibility(); } @@ -571,104 +579,79 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } }; - private final GestureDetector mGestureDetector = - new GestureDetector(new SimpleOnGestureListener() { - public boolean onDown(MotionEvent e) { - if (!isClickable()) { - mDownDetected = false; - return false; - } - - // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or - // MotionEvent.ACTION_UP (see #onTouchEvent) - if (mVibrator != null && !mDownDetected) { - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lockIcon-onDown", - VIBRATION_SONIFICATION_ATTRIBUTES); - } - - mDownDetected = true; - return true; - } - - public void onLongPress(MotionEvent e) { - if (!wasClickableOnDownEvent()) { - return; - } - - if (onAffordanceClick() && mVibrator != null) { - // only vibrate if the click went through and wasn't intercepted by falsing - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lockIcon-onLongPress", - VIBRATION_SONIFICATION_ATTRIBUTES); - } - } - - public boolean onSingleTapUp(MotionEvent e) { - if (!wasClickableOnDownEvent()) { - return false; - } - onAffordanceClick(); - return true; - } + /** + * Handles the touch if it is within the lock icon view and {@link #isActionable()} is true. + * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon + * area for {@link #LONG_PRESS_TIMEOUT} ms. + * + * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}. + */ + public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { + if (!onInterceptTouchEvent(event)) { + cancelTouches(); + return false; + } - public boolean onFling(MotionEvent e1, MotionEvent e2, - float velocityX, float velocityY) { - if (!wasClickableOnDownEvent()) { - return false; + mOnGestureDetectedRunnable = onGestureDetectedRunnable; + switch(event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_HOVER_ENTER: + if (mVibrator != null && !mDownDetected) { + if (mTick == null) { + mTick = UdfpsController.lowTick(getContext(), true, + LONG_PRESS_TIMEOUT); } - onAffordanceClick(); - return true; + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + mTick, + "lock-icon-tick", + VIBRATION_SONIFICATION_ATTRIBUTES); } - private boolean wasClickableOnDownEvent() { - return mDownDetected; + // The pointer that causes ACTION_DOWN is always at index 0. + // We need to persist its ID to track it during ACTION_MOVE that could include + // data for many other pointers because of multi-touch support. + mActivePointerId = event.getPointerId(0); + if (mVelocityTracker == null) { + // To simplify the lifecycle of the velocity tracker, make sure it's never null + // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. + mVelocityTracker = VelocityTracker.obtain(); + } else { + // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new + // ACTION_DOWN, in that case we should just reuse the old instance. + mVelocityTracker.clear(); } - - /** - * Whether we tried to launch the affordance. - * - * If falsing intercepts the click, returns false. - */ - private boolean onAffordanceClick() { - if (mFalsingManager.isFalseTouch(LOCK_ICON)) { - return false; - } - - // pre-emptively set to true to hide view - mIsBouncerShowing = true; - if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { - mAuthRippleController.showRipple(FINGERPRINT); - } - updateVisibility(); - if (mOnGestureDetectedRunnable != null) { - mOnGestureDetectedRunnable.run(); - } - mKeyguardViewController.showBouncer(/* scrim */ true); - return true; + mVelocityTracker.addMovement(event); + + mDownDetected = true; + mLongPressCancelRunnable = mExecutor.executeDelayed( + this::onLongPress, LONG_PRESS_TIMEOUT); + break; + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_HOVER_MOVE: + mVelocityTracker.addMovement(event); + // Compute pointer velocity in pixels per second. + mVelocityTracker.computeCurrentVelocity(1000); + float velocity = UdfpsController.computePointerSpeed(mVelocityTracker, + mActivePointerId); + if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS + && UdfpsController.exceedsVelocityThreshold(velocity)) { + Log.v(TAG, "lock icon long-press rescheduled due to " + + "high pointer velocity=" + velocity); + mLongPressCancelRunnable.run(); + mLongPressCancelRunnable = mExecutor.executeDelayed( + this::onLongPress, LONG_PRESS_TIMEOUT); } - }); - - /** - * Send touch events to this view and handles it if the touch is within this view and we are - * in a 'clickable' state - * @return whether to intercept the touch event - */ - public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { - if (onInterceptTouchEvent(event)) { - mOnGestureDetectedRunnable = onGestureDetectedRunnable; - mGestureDetector.onTouchEvent(event); - return true; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_HOVER_EXIT: + cancelTouches(); + break; } - mDownDetected = false; - return false; + return true; } /** @@ -676,7 +659,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme * bounds. */ public boolean onInterceptTouchEvent(MotionEvent event) { - if (!inLockIconArea(event) || !isClickable()) { + if (!inLockIconArea(event) || !isActionable()) { return false; } @@ -687,13 +670,59 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return mDownDetected; } + private void onLongPress() { + cancelTouches(); + if (mFalsingManager.isFalseTouch(LOCK_ICON)) { + Log.v(TAG, "lock icon long-press rejected by the falsing manager."); + return; + } + + // pre-emptively set to true to hide view + mIsBouncerShowing = true; + if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { + mAuthRippleController.showRipple(FINGERPRINT); + } + updateVisibility(); + if (mOnGestureDetectedRunnable != null) { + mOnGestureDetectedRunnable.run(); + } + + if (mVibrator != null) { + // play device entry haptic (same as biometric success haptic) + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-icon-device-entry", + VIBRATION_SONIFICATION_ATTRIBUTES); + } + + mKeyguardViewController.showBouncer(/* scrim */ true); + } + + + private void cancelTouches() { + mDownDetected = false; + if (mLongPressCancelRunnable != null) { + mLongPressCancelRunnable.run(); + } + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + if (mVibrator != null) { + mVibrator.cancel(); + } + } + + private boolean inLockIconArea(MotionEvent event) { return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) && (mView.getVisibility() == View.VISIBLE || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE)); } - private boolean isClickable() { + private boolean isActionable() { return mUdfpsSupported || mShowUnlockIcon; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 1226dca1f306..d8f6a01398d1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -31,6 +31,7 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PointF; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; @@ -89,6 +90,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, private static final String TAG = "AuthController"; private static final boolean DEBUG = true; + private static final int SENSOR_PRIVACY_DELAY = 500; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final CommandQueue mCommandQueue; @@ -122,6 +124,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; + private SensorPrivacyManager mSensorPrivacyManager; private class BiometricTaskStackListener extends TaskStackListener { @Override @@ -492,6 +495,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } private void updateFingerprintLocation() { @@ -642,10 +646,16 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT) || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT); + boolean isCameraPrivacyEnabled = false; + if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE + && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + mCurrentDialogArgs.argi1 /* userId */)) { + isCameraPrivacyEnabled = true; + } // TODO(b/141025588): Create separate methods for handling hard and soft errors. final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED - || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT); - + || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT + || isCameraPrivacyEnabled); if (mCurrentDialog != null) { if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { if (DEBUG) Log.d(TAG, "onBiometricError, lockout"); @@ -655,12 +665,23 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, ? mContext.getString(R.string.biometric_not_recognized) : getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage); - mCurrentDialog.onAuthenticationFailed(modality, errorMessage); + // The camera privacy error can return before the prompt initializes its state, + // causing the prompt to appear to endlessly authenticate. Add a small delay + // to stop this. + if (isCameraPrivacyEnabled) { + mHandler.postDelayed(() -> { + mCurrentDialog.onAuthenticationFailed(modality, + mContext.getString(R.string.face_sensor_privacy_enabled)); + }, SENSOR_PRIVACY_DELAY); + } else { + mCurrentDialog.onAuthenticationFailed(modality, errorMessage); + } } else { final String errorMessage = getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage); mCurrentDialog.onError(modality, errorMessage); } + } else { Log.w(TAG, "onBiometricError callback but dialog is gone"); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 09f2af4a86da..9808045d4ea8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -103,6 +103,7 @@ import kotlin.Unit; public class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; + private static final long DEFAULT_VIBRATION_DURATION = 1000; // milliseconds // Minimum required delay between consecutive touch logs in milliseconds. private static final long MIN_TOUCH_LOG_INTERVAL = 50; @@ -164,8 +165,7 @@ public class UdfpsController implements DozeReceiver { private boolean mAttemptedToDismissKeyguard; private Set<Callback> mCallbacks = new HashSet<>(); - // by default, use low tick - private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK; + private static final int DEFAULT_TICK = VibrationEffect.Composition.PRIMITIVE_LOW_TICK; private final VibrationEffect mTick; @VisibleForTesting @@ -327,12 +327,23 @@ public class UdfpsController implements DozeReceiver { } } - private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { + /** + * Calculate the pointer speed given a velocity tracker and the pointer id. + * This assumes that the velocity tracker has already been passed all relevant motion events. + */ + public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { final float vx = tracker.getXVelocity(pointerId); final float vy = tracker.getYVelocity(pointerId); return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); } + /** + * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. + */ + public static boolean exceedsVelocityThreshold(float velocity) { + return velocity > 750f; + } + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -467,7 +478,7 @@ public class UdfpsController implements DozeReceiver { final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); final float minor = event.getTouchMinor(idx); final float major = event.getTouchMajor(idx); - final boolean exceedsVelocityThreshold = v > 750f; + final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v); final String touchInfo = String.format( "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", minor, major, v, exceedsVelocityThreshold); @@ -575,7 +586,7 @@ public class UdfpsController implements DozeReceiver { mConfigurationController = configurationController; mSystemClock = systemClock; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mTick = lowTick(); + mTick = lowTick(context, false /* useShortRampup */, DEFAULT_VIBRATION_DURATION); mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -610,32 +621,43 @@ public class UdfpsController implements DozeReceiver { udfpsHapticsSimulator.setUdfpsController(this); } - private VibrationEffect lowTick() { - boolean useLowTickDefault = mContext.getResources() + /** + * Returns the continuous low tick effect that starts playing on the udfps finger-down event. + */ + public static VibrationEffect lowTick( + Context context, + boolean useShortRampUp, + long duration + ) { + boolean useLowTickDefault = context.getResources() .getBoolean(R.bool.config_udfpsUseLowTick); + int primitiveTick = DEFAULT_TICK; if (Settings.Global.getFloat( - mContext.getContentResolver(), + context.getContentResolver(), "tick-low", useLowTickDefault ? 1 : 0) == 0) { - mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK; + primitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK; } float tickIntensity = Settings.Global.getFloat( - mContext.getContentResolver(), + context.getContentResolver(), "tick-intensity", - mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity)); + context.getResources().getFloat(R.dimen.config_udfpsTickIntensity)); int tickDelay = Settings.Global.getInt( - mContext.getContentResolver(), + context.getContentResolver(), "tick-delay", - mContext.getResources().getInteger(R.integer.config_udfpsTickDelay)); + context.getResources().getInteger(R.integer.config_udfpsTickDelay)); VibrationEffect.Composition composition = VibrationEffect.startComposition(); - composition.addPrimitive(mPrimitiveTick, tickIntensity, 0); - int primitives = 1000 / tickDelay; + composition.addPrimitive(primitiveTick, tickIntensity, 0); + int primitives = (int) (duration / tickDelay); float[] rampUp = new float[]{.48f, .58f, .69f, .83f}; + if (useShortRampUp) { + rampUp = new float[]{.5f, .7f}; + } for (int i = 0; i < rampUp.length; i++) { - composition.addPrimitive(mPrimitiveTick, tickIntensity * rampUp[i], tickDelay); + composition.addPrimitive(primitiveTick, tickIntensity * rampUp[i], tickDelay); } for (int i = rampUp.length; i < primitives; i++) { - composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay); + composition.addPrimitive(primitiveTick, tickIntensity, tickDelay); } return composition.compose(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java index 71edbc06840e..d17eadd163fc 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java @@ -19,6 +19,7 @@ package com.android.systemui.classifier; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE; import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE; +import static com.android.systemui.classifier.Classifier.LOCK_ICON; import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE; import android.provider.DeviceConfig; @@ -71,7 +72,9 @@ class DiagonalClassifier extends FalsingClassifier { return Result.passed(0); } - if (interactionType == LEFT_AFFORDANCE || interactionType == RIGHT_AFFORDANCE) { + if (interactionType == LEFT_AFFORDANCE + || interactionType == RIGHT_AFFORDANCE + || interactionType == LOCK_ICON) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 03a097746ba9..c12d48d225ae 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -63,13 +63,14 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements View mDialogView; private TextView mHeaderTitle; private TextView mHeaderSubtitle; - private ImageView mHeaderIcon; private RecyclerView mDevicesRecyclerView; private LinearLayout mDeviceListLayout; private Button mDoneButton; private Button mStopButton; private int mListMaxHeight; + protected ImageView mHeaderIcon; + MediaOutputBaseAdapter mAdapter; private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> { @@ -140,7 +141,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mMediaOutputController.stop(); } - @VisibleForTesting void refresh() { // Update header icon final int iconRes = getHeaderIconRes(); @@ -154,12 +154,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } else { mHeaderIcon.setVisibility(View.GONE); } - if (mHeaderIcon.getVisibility() == View.VISIBLE) { - final int size = getHeaderIconSize(); - final int padding = mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_header_icon_padding); - mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); - } // Update title and subtitle mHeaderTitle.setText(getHeaderText()); final CharSequence subTitle = getHeaderSubtitle(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 6da4d4811ac9..ad6901f40d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dialog; +import static android.provider.Settings.ACTION_BLUETOOTH_PAIRING_SETTINGS; + import android.app.Notification; import android.content.Context; import android.content.Intent; @@ -30,6 +32,7 @@ import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -46,7 +49,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; -import com.android.settingslib.media.MediaOutputConstants; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; import com.android.systemui.animation.DialogLaunchAnimator; @@ -69,7 +71,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private static final String TAG = "MediaOutputController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - + private static final String PAGE_CONNECTED_DEVICES_KEY = + "top_level_connected_devices"; private final String mPackageName; private final Context mContext; private final MediaSessionManager mMediaSessionManager; @@ -451,14 +454,24 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mCallback.dismissDialog(); - final ActivityStarter.OnDismissAction postKeyguardAction = () -> { - mContext.sendBroadcast(new Intent() - .setAction(MediaOutputConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING) - .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)); - mShadeController.animateCollapsePanels(); - return true; - }; - mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true); + Intent launchIntent = + new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + final Intent deepLinkIntent = + new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); + if (deepLinkIntent.resolveActivity(mContext.getPackageManager()) != null) { + Log.d(TAG, "Device support split mode, launch page with deep link"); + deepLinkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + deepLinkIntent.putExtra( + Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, + launchIntent.toUri(Intent.URI_INTENT_SCHEME)); + deepLinkIntent.putExtra( + Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, + PAGE_CONNECTED_DEVICES_KEY); + mActivityStarter.startActivity(deepLinkIntent, true); + return; + } + mActivityStarter.startActivity(launchIntent, true); } void launchMediaOutputGroupDialog(View mediaOutputDialog) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java index 1300400f3b66..b41e8137c1ec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Bundle; import android.view.View; import android.view.WindowManager; +import android.widget.LinearLayout; import androidx.core.graphics.drawable.IconCompat; @@ -76,6 +77,15 @@ public class MediaOutputGroupDialog extends MediaOutputBaseDialog { } @Override + void refresh() { + super.refresh(); + final int size = getHeaderIconSize(); + final int padding = mContext.getResources().getDimensionPixelSize( + R.dimen.media_output_dialog_header_icon_padding); + mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); + } + + @Override int getStopButtonVisibility() { return View.VISIBLE; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 4d4962e7d4c2..621075cba785 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -15,6 +15,8 @@ */ package com.android.systemui.navigationbar.gestural; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; + import static com.android.systemui.classifier.Classifier.BACK_GESTURE; import android.app.ActivityManager; @@ -548,7 +550,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel); layoutParams.windowAnimations = 0; layoutParams.privateFlags |= - WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS + | PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION); layoutParams.setTitle(TAG + mContext.getDisplayId()); layoutParams.setFitInsetsTypes(0 /* types */); layoutParams.setTrustedOverlay(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index cdf770f80387..c3de3c57724f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -110,6 +110,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public int getTilesHeight() { + // Use the first page as that is the maximum height we need to show. + TileLayout tileLayout = mPages.get(0); + if (tileLayout == null) { + return 0; + } + return tileLayout.getTilesHeight(); + } + + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Pass configuration change to non-attached pages as well. Some config changes will cause diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 8588ddfcfa63..e230e1bf8051 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -27,7 +27,6 @@ import android.graphics.PointF; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; -import android.widget.ImageView; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -53,7 +52,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private float mQsExpansion; private QSCustomizer mQSCustomizer; private NonInterceptingScrollView mQSPanelContainer; - private ImageView mDragHandle; private int mSideMargins; private boolean mQsDisabled; @@ -71,7 +69,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { mQSDetail = findViewById(R.id.qs_detail); mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); - mDragHandle = findViewById(R.id.qs_drag_handle); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } @@ -190,23 +187,14 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { mQSDetail.setBottom(getTop() + scrollBottom); int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin; mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin); - // Pin the drag handle to the bottom of the panel. - mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight()); } protected int calculateContainerHeight() { int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight(); // Need to add the dragHandle height so touches will be intercepted by it. - int dragHandleHeight; - if (mDragHandle.getVisibility() == VISIBLE) { - dragHandleHeight = Math.round((1 - mQsExpansion) * mDragHandle.getHeight()); - } else { - dragHandleHeight = 0; - } return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight() : Math.round(mQsExpansion * (heightOverride - mHeader.getHeight())) - + mHeader.getHeight() - + dragHandleHeight; + + mHeader.getHeight(); } int calculateContainerBottom() { @@ -221,8 +209,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { public void setExpansion(float expansion) { mQsExpansion = expansion; mQSPanelContainer.setScrollingEnabled(expansion > 0f); - mDragHandle.setAlpha(1.0f - expansion); - mDragHandle.setClickable(expansion == 0f); // Only clickable when fully collapsed updateExpansion(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index ee59ae6ab6d7..e82e9d284bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -33,7 +33,6 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout.LayoutParams; -import android.widget.ImageView; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -94,7 +93,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private float mLastPanelFraction; private float mSquishinessFraction = 1; private boolean mQsDisabled; - private ImageView mQsDragHandler; private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final CommandQueue mCommandQueue; @@ -205,7 +203,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mHeader = view.findViewById(R.id.header); mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container)); mFooter = qsFragmentComponent.getQSFooter(); - mQsDragHandler = view.findViewById(R.id.qs_drag_handle); mQsDetailDisplayer.setQsPanelController(mQSPanelController); @@ -249,11 +246,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f); mQSAnimator.requestAnimatorUpdate(); }); - - mQsDragHandler.setOnClickListener(v -> { - Log.d(TAG, "drag handler clicked"); - mCommandQueue.animateExpandSettingsPanel(null); - }); } @Override @@ -385,30 +377,26 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } private void updateQsState() { - final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling + final boolean expanded = mQsExpanded || mInSplitShade; + final boolean expandVisually = expanded || mStackScrollerOverscrolling || mHeaderAnimating; - mQSPanelController.setExpanded(mQsExpanded); - mQSDetail.setExpanded(mQsExpanded); + mQSPanelController.setExpanded(expanded); + mQSDetail.setExpanded(expanded); boolean keyguardShowing = isKeyguardState(); - mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating + mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard) ? View.VISIBLE : View.INVISIBLE); mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) - || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController); - mFooter.setVisibility(!mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating + || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController); + mFooter.setVisibility(!mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard) ? View.VISIBLE : View.INVISIBLE); mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) - || (mQsExpanded && !mStackScrollerOverscrolling)); + || (expanded && !mStackScrollerOverscrolling)); mQSPanelController.setVisibility( !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE); - mQsDragHandler.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating - || mShowCollapsedOnKeyguard) - && Utils.shouldUseSplitNotificationShade(getResources()) - ? View.VISIBLE - : View.GONE); } private boolean isKeyguardState() { @@ -418,7 +406,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } private void updateShowCollapsedOnKeyguard() { - boolean showCollapsed = mBypassController.getBypassEnabled() || mTransitioningToFullShade; + boolean showCollapsed = mBypassController.getBypassEnabled() + || (mTransitioningToFullShade && !mInSplitShade); if (showCollapsed != mShowCollapsedOnKeyguard) { mShowCollapsedOnKeyguard = showCollapsed; updateQsState(); @@ -498,6 +487,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setInSplitShade(boolean inSplitShade) { mInSplitShade = inSplitShade; mQSAnimator.setTranslateWhileExpanding(inSplitShade); + updateShowCollapsedOnKeyguard(); + updateQsState(); } @Override @@ -516,7 +507,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction) { float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; - float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction; + float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD + ? mFullShadeProgress : panelExpansionFraction; setAlphaAnimationProgress(mInSplitShade ? progress : 1); mContainer.setExpansion(expansion); final float translationScaleY = (mInSplitShade diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index d69deefc3477..20c0fdd7de89 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -258,13 +258,8 @@ public class QSPanel extends LinearLayout implements Tunable { } private void updateViewPositions() { - if (!(mTileLayout instanceof TileLayout)) { - return; - } - TileLayout layout = (TileLayout) mTileLayout; - // Adjust view positions based on tile squishing - int tileHeightOffset = layout.getTilesHeight() - layout.getHeight(); + int tileHeightOffset = mTileLayout.getTilesHeight() - mTileLayout.getHeight(); boolean move = false; for (int i = 0; i < getChildCount(); i++) { @@ -787,6 +782,12 @@ public class QSPanel extends LinearLayout implements Tunable { /** */ void setListening(boolean listening, UiEventLogger uiEventLogger); + /** */ + int getHeight(); + + /** */ + int getTilesHeight(); + /** * Sets a size modifier for the tile. Where 0 means collapsed, and 1 expanded. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 6794d5b0cee4..001c740e310a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -45,6 +45,7 @@ import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.util.Utils; import javax.inject.Inject; import javax.inject.Named; @@ -72,6 +73,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { new QSPanel.OnConfigurationChangedListener() { @Override public void onConfigurationChange(Configuration newConfig) { + updateMediaExpansion(); mView.updateResources(); mQsSecurityFooter.onConfigurationChanged(); if (mView.isListening()) { @@ -121,13 +123,17 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override public void onInit() { super.onInit(); - mMediaHost.setExpansion(1); + updateMediaExpansion(); mMediaHost.setShowsOnlyActiveMedia(false); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); mQsCustomizerController.init(); mBrightnessSliderController.init(); } + private void updateMediaExpansion() { + mMediaHost.setExpansion(Utils.shouldUseSplitNotificationShade(getResources()) ? 0 : 1); + } + @Override protected void onViewAttached() { super.onViewAttached(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 7f08e5bdb575..bff318a6f44e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -275,6 +275,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { return Math.max(mColumns * mRows, 1); } + @Override public int getTilesHeight() { return mLastTileBottom + getPaddingBottom(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 26c89ff83076..ba4257f52892 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -78,7 +78,7 @@ public class InternetDialog extends SystemUIDialog implements private static final String TAG = "InternetDialog"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - static final long PROGRESS_DELAY_MS = 2000L; + static final long PROGRESS_DELAY_MS = 1500L; private final Handler mHandler; private final Executor mBackgroundExecutor; @@ -137,6 +137,8 @@ public class InternetDialog extends SystemUIDialog implements protected WifiEntry mConnectedWifiEntry; @VisibleForTesting protected int mWifiEntriesCount; + @VisibleForTesting + protected boolean mHasMoreEntry; // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; @@ -464,8 +466,7 @@ public class InternetDialog extends SystemUIDialog implements } mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount()); mWifiRecyclerView.setVisibility(View.VISIBLE); - final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0; - mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE); + mSeeAllLayout.setVisibility(mHasMoreEntry ? View.VISIBLE : View.INVISIBLE); } @VisibleForTesting @@ -549,9 +550,13 @@ public class InternetDialog extends SystemUIDialog implements } private void setProgressBarVisible(boolean visible) { + if (mIsProgressBarVisible == visible) { + return; + } mIsProgressBarVisible = visible; - mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE); - mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE); + mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE); + mProgressBar.setIndeterminate(visible); + mDivider.setVisibility(visible ? View.GONE : View.VISIBLE); mInternetDialogSubTitle.setText(getSubtitleText()); } @@ -651,13 +656,14 @@ public class InternetDialog extends SystemUIDialog implements @Override @WorkerThread public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, - @Nullable WifiEntry connectedEntry) { + @Nullable WifiEntry connectedEntry, boolean hasMoreEntry) { // Should update the carrier network layout when it is connected under airplane mode ON. boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE && mInternetDialogController.isAirplaneModeEnabled(); mHandler.post(() -> { mConnectedWifiEntry = connectedEntry; mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); + mHasMoreEntry = hasMoreEntry; updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */); mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount); mAdapter.notifyDataSetChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 706f4230bf9f..1fee1b430073 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -86,6 +86,7 @@ import com.android.systemui.util.settings.GlobalSettings; import com.android.wifitrackerlib.MergedCarrierEntry; import com.android.wifitrackerlib.WifiEntry; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -98,8 +99,10 @@ import java.util.stream.Stream; import javax.inject.Inject; -public class InternetDialogController implements WifiEntry.DisconnectCallback, - AccessPointController.AccessPointCallback { +/** + * Controller for Internet Dialog. + */ +public class InternetDialogController implements AccessPointController.AccessPointCallback { private static final String TAG = "InternetDialogController"; private static final String ACTION_NETWORK_PROVIDER_SETTINGS = @@ -345,6 +348,10 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS); } + if (isCarrierNetworkActive()) { + return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE); + } + // Sub-Title: // show non_carrier_network_unavailable // - while Wi-Fi on + no Wi-Fi item @@ -876,44 +883,42 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, mConnectedEntry = null; mWifiEntriesCount = 0; if (mCallback != null) { - mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */); + mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */, + false /* hasMoreEntry */); } 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; - } - + boolean hasMoreEntry = false; int count = MAX_WIFI_ENTRY_COUNT; if (mHasEthernet) { count -= 1; } - if (hasActiveSubId()) { + if (hasActiveSubId() || isCarrierNetworkActive()) { count -= 1; } - if (hasConnectedWifi) { - count -= 1; + final int wifiTotalCount = accessPoints.size(); + if (count > wifiTotalCount) { + count = wifiTotalCount; + } else if (count < wifiTotalCount) { + hasMoreEntry = true; } - final List<WifiEntry> wifiEntries = accessPoints.stream() - .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork() - || !wifiEntry.hasInternetAccess())) - .limit(count) - .collect(Collectors.toList()); - mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); + + WifiEntry connectedEntry = null; + final List<WifiEntry> wifiEntries = new ArrayList<>(); + for (int i = 0; i < count; i++) { + WifiEntry entry = accessPoints.get(i); + if (connectedEntry == null && entry.isDefaultNetwork() && entry.hasInternetAccess()) { + connectedEntry = entry; + } else { + wifiEntries.add(entry); + } + } + mConnectedEntry = connectedEntry; + mWifiEntriesCount = wifiEntries.size(); if (mCallback != null) { - mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry); + mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry); } } @@ -921,10 +926,6 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, public void onSettingsActivityTriggered(Intent settingsIntent) { } - @Override - public void onDisconnectResult(int status) { - } - private class InternetTelephonyCallback extends TelephonyCallback implements TelephonyCallback.DataConnectionStateListener, TelephonyCallback.DisplayInfoListener, @@ -1068,7 +1069,7 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, void dismissDialog(); void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, - @Nullable WifiEntry connectedEntry); + @Nullable WifiEntry connectedEntry, boolean hasMoreEntry); } void makeOverlayToast(int stringId) { diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt index c50365f1bf38..71c5fad5322e 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt @@ -15,7 +15,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog class SensorUseDialog( context: Context, val sensor: Int, - val clickListener: DialogInterface.OnClickListener + val clickListener: DialogInterface.OnClickListener, + val dismissListener: DialogInterface.OnDismissListener ) : SystemUIDialog(context) { // TODO move to onCreate (b/200815309) @@ -69,6 +70,8 @@ class SensorUseDialog( context.getString(com.android.internal.R.string .cancel), clickListener) + setOnDismissListener(dismissListener) + 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 b0071d92481d..dae375ad7cc7 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -50,7 +50,7 @@ class SensorUseStartedActivity @Inject constructor( private val keyguardStateController: KeyguardStateController, private val keyguardDismissUtil: KeyguardDismissUtil, @Background private val bgHandler: Handler -) : Activity(), DialogInterface.OnClickListener { +) : Activity(), DialogInterface.OnClickListener, DialogInterface.OnDismissListener { companion object { private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName @@ -120,7 +120,7 @@ class SensorUseStartedActivity @Inject constructor( } } - mDialog = SensorUseDialog(this, sensor, this) + mDialog = SensorUseDialog(this, sensor, this, this) mDialog!!.show() } @@ -212,4 +212,8 @@ class SensorUseStartedActivity @Inject constructor( .suppressSensorPrivacyReminders(sensor, suppressed) } } + + override fun onDismiss(dialog: DialogInterface?) { + finish() + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index dca7f70d3470..0fb08e403483 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -174,7 +174,7 @@ class LockscreenShadeTransitionController @Inject constructor( internal fun canDragDown(): Boolean { return (statusBarStateController.state == StatusBarState.KEYGUARD || nsslController.isInLockedDownShade()) && - qS.isFullyCollapsed + (qS.isFullyCollapsed || useSplitShade) } /** @@ -285,7 +285,7 @@ class LockscreenShadeTransitionController @Inject constructor( internal val isDragDownAnywhereEnabled: Boolean get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD && !keyguardBypassController.bypassEnabled && - qS.isFullyCollapsed) + (qS.isFullyCollapsed || useSplitShade)) /** * The amount in pixels that the user has dragged down. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index 103ca0ebc6ca..ff9d919dda13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -83,7 +83,7 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> @Override public void notifyListeners(SignalCallback callback) { if (mCurrentState.isCarrierMerged) { - if (mCurrentState.isDefault) { + if (mCurrentState.isDefault || !mNetworkController.isRadioOn()) { notifyListenersForCarrierWifi(callback); } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index 1d921702e632..f2d926d97108 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -25,7 +25,6 @@ import android.service.dreams.IDreamManager; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; -import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; @@ -301,24 +300,15 @@ public interface StatusBarDependenciesModule { */ @Provides @SysUISingleton - static LaunchAnimator provideLaunchAnimator(Context context) { - return new LaunchAnimator(context); + static ActivityLaunchAnimator provideActivityLaunchAnimator() { + return new ActivityLaunchAnimator(); } /** */ @Provides @SysUISingleton - static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) { - return new ActivityLaunchAnimator(launchAnimator); - } - - /** - */ - @Provides - @SysUISingleton - static DialogLaunchAnimator provideDialogLaunchAnimator(Context context, - LaunchAnimator launchAnimator, IDreamManager dreamManager) { - return new DialogLaunchAnimator(context, launchAnimator, dreamManager); + static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) { + return new DialogLaunchAnimator(dreamManager); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt index 64a73054c434..349b1918a504 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt @@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification import android.util.MathUtils import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.Interpolators import com.android.systemui.animation.LaunchAnimator import kotlin.math.min @@ -55,6 +56,7 @@ class ExpandAnimationParameters( } fun getProgress(delay: Long, duration: Long): Float { - return LaunchAnimator.getProgress(linearProgress, delay, duration) + return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay, + duration) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 4441270f895b..fa9217d831f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.Trace; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; @@ -158,6 +159,9 @@ public class NotificationLogger implements StateListener { mExpansionStateLogger.onVisibilityChanged( mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications); + Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Active]", N); + Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Visible]", + mCurrentlyVisibleNotifications.size()); recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); mTmpCurrentlyVisibleNotifications.clear(); 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 ab08865c8bef..9167b08ecd0c 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 @@ -119,6 +119,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -681,6 +682,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0) && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD + && mQsExpansionFraction != 1 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && !mIsRemoteInputActive; boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), @@ -726,36 +728,57 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } if (DEBUG) { - int y = mTopPadding; - mDebugPaint.setColor(Color.RED); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + onDrawDebug(canvas); + } + } - y = getLayoutHeight(); - mDebugPaint.setColor(Color.YELLOW); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + /** Used to track the Y positions that were already used to draw debug text labels. */ + private static final Set<Integer> DEBUG_TEXT_USED_Y_POSITIONS = + DEBUG ? new HashSet<>() : Collections.emptySet(); - y = (int) mMaxLayoutHeight; - mDebugPaint.setColor(Color.MAGENTA); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + private void onDrawDebug(Canvas canvas) { + DEBUG_TEXT_USED_Y_POSITIONS.clear(); - if (mKeyguardBottomPadding >= 0) { - y = getHeight() - (int) mKeyguardBottomPadding; - mDebugPaint.setColor(Color.GRAY); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); - } + int y = mTopPadding; + drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding"); - y = getHeight() - getEmptyBottomMargin(); - mDebugPaint.setColor(Color.GREEN); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + y = getLayoutHeight(); + drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight()"); - y = (int) (mAmbientState.getStackY()); - mDebugPaint.setColor(Color.CYAN); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + y = (int) mMaxLayoutHeight; + drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight"); - y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); - mDebugPaint.setColor(Color.BLUE); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + if (mKeyguardBottomPadding >= 0) { + y = getHeight() - (int) mKeyguardBottomPadding; + drawDebugInfo(canvas, y, Color.GRAY, + /* label= */ "getHeight() - mKeyguardBottomPadding"); } + + y = getHeight() - getEmptyBottomMargin(); + drawDebugInfo(canvas, y, Color.GREEN, /* label= */ "getHeight() - getEmptyBottomMargin()"); + + y = (int) (mAmbientState.getStackY()); + drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY()"); + + y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); + drawDebugInfo(canvas, y, Color.BLUE, + /* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight()"); + } + + private void drawDebugInfo(Canvas canvas, int y, int color, String label) { + mDebugPaint.setColor(color); + canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ getWidth(), /* stopY= */ y, + mDebugPaint); + canvas.drawText(label, /* x= */ 0, /* y= */ computeDebugYTextPosition(y), mDebugPaint); + } + + private int computeDebugYTextPosition(int lineY) { + int textY = lineY; + while (DEBUG_TEXT_USED_Y_POSITIONS.contains(textY)) { + textY = (int) (textY + mDebugPaint.getTextSize()); + } + DEBUG_TEXT_USED_Y_POSITIONS.add(textY); + return textY; } @ShadeViewRefactor(RefactorComponent.DECORATOR) @@ -4744,6 +4767,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsExpansionFraction(float qsExpansionFraction) { + boolean footerAffected = mQsExpansionFraction != qsExpansionFraction + && (mQsExpansionFraction == 1 || qsExpansionFraction == 1); mQsExpansionFraction = qsExpansionFraction; updateUseRoundedRectClipping(); @@ -4752,6 +4777,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mOwnScrollY > 0) { setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction)); } + if (footerAffected) { + updateFooter(); + } } @ShadeViewRefactor(RefactorComponent.COORDINATOR) 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 0312c30c73ec..261b5dbac3d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -110,6 +110,7 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.biometrics.AuthController; @@ -225,7 +226,8 @@ public class NotificationPanelViewController extends PanelViewController { */ private static final int FLING_HIDE = 2; private static final long ANIMATION_DELAY_ICON_FADE_IN = - LaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION + ActivityLaunchAnimator.TIMINGS.getTotalDuration() + - CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY - 48; private final DozeParameters mDozeParameters; @@ -2246,11 +2248,14 @@ public class NotificationPanelViewController extends PanelViewController { private void updateQsExpansion() { if (mQs == null) return; - float qsExpansionFraction = computeQsExpansionFraction(); - float squishiness = mNotificationStackScrollLayoutController - .getNotificationSquishinessFraction(); - mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation(), - mQsExpandImmediate || mQsExpanded ? 1f : squishiness); + final float squishiness = + mQsExpandImmediate || mQsExpanded ? 1f : mNotificationStackScrollLayoutController + .getNotificationSquishinessFraction(); + final float qsExpansionFraction = computeQsExpansionFraction(); + final float adjustedExpansionFraction = mShouldUseSplitNotificationShade + ? 1f : computeQsExpansionFraction(); + mQs.setQsExpansion(adjustedExpansionFraction, getExpandedFraction(), getHeaderTranslation(), + squishiness); mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); @@ -3626,8 +3631,8 @@ public class NotificationPanelViewController extends PanelViewController { } public void applyLaunchAnimationProgress(float linearProgress) { - boolean hideIcons = LaunchAnimator.getProgress(linearProgress, - ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; + boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, + linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; if (hideIcons != mHideIconsDuringLaunchAnimation) { mHideIconsDuringLaunchAnimation = hideIcons; if (!hideIcons) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index 32aae6c05df6..2ba37c2ec29f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -23,7 +23,8 @@ class StatusBarLaunchAnimatorController( delegate.onLaunchAnimationStart(isExpandingFullyAbove) statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true) if (!isExpandingFullyAbove) { - statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt()) + statusBar.collapsePanelWithDuration( + ActivityLaunchAnimator.TIMINGS.totalDuration.toInt()) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java deleted file mode 100644 index ac8b47dc7e0b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2016 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.app.StatusBarManager; -import android.content.Context; -import android.content.res.Configuration; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.qs.QSFragment; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.phone.StatusBar; - -import javax.inject.Inject; - -/** - * Let {@link RemoteInputView} to control the visibility of QuickSetting. - */ -@SysUISingleton -public class RemoteInputQuickSettingsDisabler - implements ConfigurationController.ConfigurationListener { - - private Context mContext; - @VisibleForTesting boolean mRemoteInputActive; - @VisibleForTesting boolean misLandscape; - private int mLastOrientation; - private final CommandQueue mCommandQueue; - - @Inject - public RemoteInputQuickSettingsDisabler(Context context, - ConfigurationController configController, CommandQueue commandQueue) { - mContext = context; - mCommandQueue = commandQueue; - mLastOrientation = mContext.getResources().getConfiguration().orientation; - configController.addCallback(this); - } - - public int adjustDisableFlags(int state) { - if (mRemoteInputActive && misLandscape) { - state |= StatusBarManager.DISABLE2_QUICK_SETTINGS; - } - - return state; - } - - public void setRemoteInputActive(boolean active){ - if(mRemoteInputActive != active){ - mRemoteInputActive = active; - recomputeDisableFlags(); - } - } - - @Override - public void onConfigChanged(Configuration newConfig) { - if (newConfig.orientation != mLastOrientation) { - misLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; - mLastOrientation = newConfig.orientation; - recomputeDisableFlags(); - } - } - - /** - * Reapplies the disable flags. Then the method adjustDisableFlags in this class will be invoked - * in {@link QSFragment#disable(int, int, boolean)} and - * {@link StatusBar#disable(int, int, boolean)} - * to modify the disable flags according to the status of mRemoteInputActive and misLandscape. - */ - private void recomputeDisableFlags() { - mCommandQueue.recomputeDisableFlags(mContext.getDisplayId(), true); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt new file mode 100644 index 000000000000..31ef2f64e4a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 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.app.StatusBarManager +import android.content.Context +import android.content.res.Configuration +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.Utils +import javax.inject.Inject + +/** + * Controls whether the disable flag [StatusBarManager.DISABLE2_QUICK_SETTINGS] should be set. + * This would happen when a [RemoteInputView] is active, the device is in landscape and not using + * split shade. + */ +@SysUISingleton +class RemoteInputQuickSettingsDisabler @Inject constructor( + private val context: Context, + private val commandQueue: CommandQueue, + configController: ConfigurationController +) : ConfigurationController.ConfigurationListener { + + private var remoteInputActive = false + private var isLandscape: Boolean + private var shouldUseSplitNotificationShade: Boolean + + init { + isLandscape = + context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + shouldUseSplitNotificationShade = Utils.shouldUseSplitNotificationShade(context.resources) + configController.addCallback(this) + } + + fun adjustDisableFlags(state: Int): Int { + var mutableState = state + if (remoteInputActive && + isLandscape && + !shouldUseSplitNotificationShade + ) { + mutableState = state or StatusBarManager.DISABLE2_QUICK_SETTINGS + } + return mutableState + } + + fun setRemoteInputActive(active: Boolean) { + if (remoteInputActive != active) { + remoteInputActive = active + recomputeDisableFlags() + } + } + + override fun onConfigChanged(newConfig: Configuration) { + var needToRecompute = false + + val newIsLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE + if (newIsLandscape != isLandscape) { + isLandscape = newIsLandscape + needToRecompute = true + } + + val newSplitShadeFlag = Utils.shouldUseSplitNotificationShade(context.resources) + if (newSplitShadeFlag != shouldUseSplitNotificationShade) { + shouldUseSplitNotificationShade = newSplitShadeFlag + needToRecompute = true + } + if (needToRecompute) { + recomputeDisableFlags() + } + } + + /** + * Called in order to trigger a refresh of the disable flags after a relevant configuration + * change or when a [RemoteInputView] has changed its active state. The method + * [adjustDisableFlags] will be invoked to modify the disable flags according to + * [remoteInputActive], [isLandscape] and [shouldUseSplitNotificationShade]. + */ + private fun recomputeDisableFlags() { + commandQueue.recomputeDisableFlags(context.displayId, true) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 51de132108be..e6fc49fae315 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.unfold +import android.animation.ValueAnimator import android.content.Context import android.graphics.PixelFormat import android.hardware.devicestate.DeviceStateManager @@ -111,7 +112,7 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn") try { // Add the view only if we are unfolding and this is the first screen on - if (!isFolded && !isUnfoldHandled) { + if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) { addView(onOverlayReady) isUnfoldHandled = true } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index d819fa2adc38..1fe3d4417730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -46,7 +46,7 @@ import org.mockito.junit.MockitoJUnit @RunWithLooper class ActivityLaunchAnimatorTest : SysuiTestCase() { private val launchContainer = LinearLayout(mContext) - private val launchAnimator = LaunchAnimator(mContext, isForTesting = true) + private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) @Mock lateinit var callback: ActivityLaunchAnimator.Callback @Spy private val controller = TestLaunchAnimatorController(launchContainer) @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index f9ad740f86df..b951345a145b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -33,7 +33,7 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class DialogLaunchAnimatorTest : SysuiTestCase() { - private val launchAnimator = LaunchAnimator(context, isForTesting = true) + private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) private lateinit var dialogLaunchAnimator: DialogLaunchAnimator private val attachedViews = mutableSetOf<View>() @@ -42,7 +42,8 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { @Before fun setUp() { - dialogLaunchAnimator = DialogLaunchAnimator(context, launchAnimator, dreamManager) + dialogLaunchAnimator = DialogLaunchAnimator( + dreamManager, launchAnimator, isForTesting = true) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt new file mode 100644 index 000000000000..dadf94e2a9dd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt @@ -0,0 +1,23 @@ +package com.android.systemui.animation + +/** + * A [LaunchAnimator.Timings] to be used in tests. + * + * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions + * when computing the progress of a sub-animation (the contents fade in/out). + */ +val TEST_TIMINGS = LaunchAnimator.Timings( + totalDuration = 0L, + contentBeforeFadeOutDelay = 1L, + contentBeforeFadeOutDuration = 1L, + contentAfterFadeInDelay = 1L, + contentAfterFadeInDuration = 1L +) + +/** A [LaunchAnimator.Interpolators] to be used in tests. */ +val TEST_INTERPOLATORS = LaunchAnimator.Interpolators( + positionInterpolator = Interpolators.STANDARD, + positionXInterpolator = Interpolators.STANDARD, + contentBeforeFadeOutInterpolator = Interpolators.STANDARD, + contentAfterFadeInInterpolator = Interpolators.STANDARD +)
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index e01583e1cb1e..81bcbfb1f460 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -16,12 +16,14 @@ package com.android.systemui.keyguard; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.keyguard.LockIconView.ICON_LOCK; import static com.android.keyguard.LockIconView.ICON_UNLOCK; import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; @@ -57,6 +59,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.doze.util.BurnInHelperKt; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -68,6 +71,7 @@ import com.android.systemui.util.time.FakeSystemClock; import com.airbnb.lottie.LottieAnimationView; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -76,6 +80,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.List; @@ -86,6 +92,8 @@ import java.util.List; public class LockIconViewControllerTest extends SysuiTestCase { private static final String UNLOCKED_LABEL = "unlocked"; + private MockitoSession mStaticMockSession; + private @Mock LockIconView mLockIconView; private @Mock AnimatedStateListDrawable mIconDrawable; private @Mock Context mContext; @@ -133,6 +141,10 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + mStaticMockSession = mockitoSession() + .mockStatic(BurnInHelperKt.class) + .strictness(Strictness.LENIENT) + .startMocking(); MockitoAnnotations.initMocks(this); when(mLockIconView.getResources()).thenReturn(mResources); @@ -169,6 +181,11 @@ public class LockIconViewControllerTest extends SysuiTestCase { ); } + @After + public void tearDown() { + mStaticMockSession.finishMocking(); + } + @Test public void testIgnoreUdfpsWhenNotSupported() { // GIVEN Udpfs sensor is NOT available @@ -369,6 +386,42 @@ public class LockIconViewControllerTest extends SysuiTestCase { verify(mLockIconView).updateIcon(ICON_LOCK, true); } + @Test + public void testBurnInOffsetsUpdated_onDozeAmountChanged() { + // GIVEN udfps enrolled + setupUdfps(); + when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); + + // GIVEN burn-in offset = 5 + int burnInOffset = 5; + when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset); + + // GIVEN starting state for the lock icon (keyguard) + setupShowLockIcon(); + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + captureStatusBarStateListener(); + reset(mLockIconView); + + // WHEN dozing updates + mStatusBarStateListener.onDozingChanged(true /* isDozing */); + mStatusBarStateListener.onDozeAmountChanged(1f, 1f); + + // THEN the view's translation is updated to use the AoD burn-in offsets + verify(mLockIconView).setTranslationY(burnInOffset); + verify(mLockIconView).setTranslationX(burnInOffset); + reset(mLockIconView); + + // WHEN the device is no longer dozing + mStatusBarStateListener.onDozingChanged(false /* isDozing */); + mStatusBarStateListener.onDozeAmountChanged(0f, 0f); + + // THEN the view is updated to NO translation (no burn-in offsets anymore) + verify(mLockIconView).setTranslationY(0); + verify(mLockIconView).setTranslationX(0); + + } private Pair<Integer, PointF> setupUdfps() { when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); final PointF udfpsLocation = new PointF(50, 75); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 30664ba3c5c0..5b9a7f3bd158 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -173,8 +173,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { protected Fragment instantiate(Context context, String className, Bundle arguments) { CommandQueue commandQueue = new CommandQueue(context); return new QSFragment( - new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class), - commandQueue), + new RemoteInputQuickSettingsDisabler(context, commandQueue, + mock(ConfigurationController.class)), mock(QSTileHost.class), mock(StatusBarStateController.class), commandQueue, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index ca8903bfe009..6dca2a73b57b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -1,5 +1,7 @@ package com.android.systemui.qs.tiles.dialog; +import static android.provider.Settings.Global.AIRPLANE_MODE_ON; + import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT; @@ -19,7 +21,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.animation.Animator; -import android.content.Context; import android.content.Intent; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; @@ -37,7 +38,6 @@ import android.view.Gravity; import android.view.View; import android.view.WindowManager; -import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -47,7 +47,6 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -70,7 +69,6 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Executor; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -78,8 +76,6 @@ import java.util.concurrent.Executor; public class InternetDialogControllerTest extends SysuiTestCase { private static final int SUB_ID = 1; - private static final String CONNECTED_TITLE = "Connected Wi-Fi Title"; - private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary"; //SystemUIToast private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL; @@ -142,7 +138,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { private DialogLaunchAnimator mDialogLaunchAnimator; private TestableResources mTestableResources; - private MockInternetDialogController mInternetDialogController; + private InternetDialogController mInternetDialogController; private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private List<WifiEntry> mAccessPoints = new ArrayList<>(); private List<WifiEntry> mWifiEntries = new ArrayList<>(); @@ -163,14 +159,13 @@ public class InternetDialogControllerTest extends SysuiTestCase { mAccessPoints.add(mWifiEntry1); when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); - when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt())) .thenReturn(mSystemUIToast); when(mSystemUIToast.getView()).thenReturn(mToastView); when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS); when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator); - mInternetDialogController = new MockInternetDialogController(mContext, + mInternetDialogController = new InternetDialogController(mContext, mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController, mSubscriptionManager, mTelephonyManager, mWifiManager, mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher, @@ -225,7 +220,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() { - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(), getResourcesString("airplane_mode"))); @@ -233,7 +228,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getDialogTitleText_withAirplaneModeOff_returnInternet() { - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(), getResourcesString("quick_settings_internet_label"))); @@ -241,14 +236,14 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withAirplaneModeOn_returnNull() { - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); assertThat(mInternetDialogController.getSubtitleText(false)).isNull(); } @Test public void getSubtitleText_withWifiOff_returnWifiIsOff() { - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); when(mWifiManager.isWifiEnabled()).thenReturn(false); assertThat(mInternetDialogController.getSubtitleText(false)) @@ -263,7 +258,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withNoWifiEntry_returnSearchWifi() { - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); when(mWifiManager.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); @@ -280,7 +275,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withWifiEntry_returnTapToConnect() { // The preconditions WiFi Entries is already in setUp() - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); when(mWifiManager.isWifiEnabled()).thenReturn(true); assertThat(mInternetDialogController.getSubtitleText(false)) @@ -295,7 +290,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() { - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); when(mWifiManager.isWifiEnabled()).thenReturn(true); when(mKeyguardStateController.isUnlocked()).thenReturn(false); @@ -305,7 +300,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withNoService_returnNoNetworksAvailable() { - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); when(mWifiManager.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); @@ -319,7 +314,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() { - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); when(mWifiManager.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); @@ -339,6 +334,17 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @Test + public void getSubtitleText_withCarrierNetworkActiveOnly_returnNoOtherAvailable() { + fakeAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(true); + mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); + when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true); + + assertThat(mInternetDialogController.getSubtitleText(false)) + .isEqualTo(getResourcesString("non_carrier_network_unavailable")); + } + + @Test public void getWifiDetailsSettingsIntent_withNoKey_returnNull() { assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull(); } @@ -404,7 +410,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); - verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any()); + verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any(), anyBoolean()); } @Test @@ -413,27 +419,28 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); - verify(mInternetDialogCallback) - .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(null /* wifiEntries */, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() { reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); mAccessPoints.clear(); mAccessPoints.add(mConnectedEntry); mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.clear(); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() { reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); mAccessPoints.clear(); mAccessPoints.add(mWifiEntry1); @@ -441,14 +448,14 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() { reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); mAccessPoints.clear(); mAccessPoints.add(mConnectedEntry); mAccessPoints.add(mWifiEntry1); @@ -457,13 +464,14 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() { reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); mAccessPoints.clear(); mAccessPoints.add(mConnectedEntry); mAccessPoints.add(mWifiEntry1); @@ -474,13 +482,14 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() { reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); mAccessPoints.clear(); mAccessPoints.add(mConnectedEntry); mAccessPoints.add(mWifiEntry1); @@ -493,22 +502,24 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); } @Test public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() { reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); mAccessPoints.clear(); mAccessPoints.add(mConnectedEntry); mAccessPoints.add(mWifiEntry1); @@ -522,22 +533,54 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); + } + + @Test + public void onAccessPointsChanged_oneCarrierWifiAndFourOthers_callbackCutMore() { + reset(mInternetDialogCallback); + fakeAirplaneModeEnabled(true); + when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true); + mAccessPoints.clear(); + mAccessPoints.add(mWifiEntry1); + mAccessPoints.add(mWifiEntry2); + mAccessPoints.add(mWifiEntry3); + mAccessPoints.add(mWifiEntry4); + + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + + mWifiEntries.clear(); + mWifiEntries.add(mWifiEntry1); + mWifiEntries.add(mWifiEntry2); + mWifiEntries.add(mWifiEntry3); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); + + // Turn off airplane mode to has carrier WiFi, then Wi-Fi entries will keep the same. + reset(mInternetDialogCallback); + fakeAirplaneModeEnabled(false); + + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); } @Test public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() { reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(true); + fakeAirplaneModeEnabled(true); mAccessPoints.clear(); mAccessPoints.add(mWifiEntry1); mAccessPoints.add(mWifiEntry2); @@ -551,8 +594,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); mWifiEntries.add(mWifiEntry4); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); // If the Ethernet exists, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -561,18 +604,35 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry4); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); - mInternetDialogController.setAirplaneModeEnabled(false); + fakeAirplaneModeEnabled(false); mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); + } + + @Test + public void onAccessPointsChanged_wifiIsDefaultButNoInternetAccess_putIntoWifiEntries() { + reset(mInternetDialogCallback); + mAccessPoints.clear(); + when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mWifiEntry1.isDefaultNetwork()).thenReturn(true); + when(mWifiEntry1.hasInternetAccess()).thenReturn(false); + mAccessPoints.add(mWifiEntry1); + + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + + mWifiEntries.clear(); + mWifiEntries.add(mWifiEntry1); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test @@ -641,38 +701,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mContext.getPackageName()); } - private class MockInternetDialogController extends InternetDialogController { - - private GlobalSettings mGlobalSettings; - private boolean mIsAirplaneModeOn; - - MockInternetDialogController(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, Handler workerHandler, - CarrierConfigTracker carrierConfigTracker, - LocationController locationController, - DialogLaunchAnimator dialogLaunchAnimator) { - super(context, uiEventLogger, starter, accessPointController, subscriptionManager, - telephonyManager, wifiManager, connectivityManager, handler, mainExecutor, - broadcastDispatcher, keyguardUpdateMonitor, globalSettings, - keyguardStateController, windowManager, toastFactory, workerHandler, - carrierConfigTracker, locationController, dialogLaunchAnimator); - mGlobalSettings = globalSettings; - } - - @Override - boolean isAirplaneModeEnabled() { - return mIsAirplaneModeOn; - } - - public void setAirplaneModeEnabled(boolean enabled) { - mIsAirplaneModeOn = enabled; - } + private void fakeAirplaneModeEnabled(boolean enabled) { + when(mGlobalSettings.getInt(eq(AIRPLANE_MODE_ON), anyInt())).thenReturn(enabled ? 1 : 0); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 0cf063f5db39..651bcdef9978 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -316,6 +316,20 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() { + // The precondition WiFi ON is already in setUp() + mInternetDialog.mConnectedWifiEntry = null; + mInternetDialog.mWifiEntriesCount = 1; + + mInternetDialog.updateDialog(false); + + assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); + // Show a blank block to fix the dialog height even if there is no WiFi list + assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mWifiEntriesCount = 0; @@ -325,13 +339,15 @@ public class InternetDialogTest extends SysuiTestCase { assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); // Show a blank block to fix the dialog height even if there is no WiFi list assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); } @Test - public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() { + public void updateDialog_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mConnectedWifiEntry = null; + mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT; + mInternetDialog.mHasMoreEntry = true; mInternetDialog.updateDialog(false); @@ -343,6 +359,8 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() + mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1; + mInternetDialog.mHasMoreEntry = true; mInternetDialog.updateDialog(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 793851160dc2..89435ae164b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -6,6 +6,7 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.util.DisplayMetrics import com.android.systemui.ExpandHelper +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.media.MediaHierarchyManager @@ -81,6 +82,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { mDependency, TestableLooper.get(this)) row = helper.createRow() + context.getOrCreateTestableResources() + .addOverride(R.bool.config_use_split_notification_shade, false) transitionController = LockscreenShadeTransitionController( statusBarStateController = statusbarStateController, lockscreenGestureLogger = lockscreenGestureLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index a39971d27303..9f152e1e2fe5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -269,6 +269,21 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } @Test + public void testDisableWiFiWithVcnWithUnderlyingWifi() { + String testSsid = "Test VCN SSID"; + setWifiEnabled(true); + verifyLastWifiIcon(false, WifiIcons.WIFI_NO_NETWORK); + + mNetworkController.setNoNetworksAvailable(false); + setWifiStateForVcn(true, testSsid); + setWifiLevelForVcn(1); + verifyLastMobileDataIndicatorsForVcn(true, 1, TelephonyIcons.ICON_CWF, false); + + setWifiEnabled(false); + verifyLastMobileDataIndicatorsForVcn(false, 1, 0, false); + } + + @Test public void testCallStrengh() { if (true) return; String testSsid = "Test SSID"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java deleted file mode 100644 index b359b9ce6287..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java +++ /dev/null @@ -1,95 +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.statusbar.policy; - -import static junit.framework.TestCase.assertTrue; - -import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.content.res.Configuration; -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.policy.ConfigurationController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class RemoteInputQuickSettingsDisablerTest extends SysuiTestCase { - - @Mock - private CommandQueue mCommandQueue; - private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mRemoteInputQuickSettingsDisabler = new RemoteInputQuickSettingsDisabler(mContext, - mock(ConfigurationController.class), mCommandQueue); - } - - @Test - public void shouldEnableQuickSetting_afterDeactiviate() { - mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE); - mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE); - assertFalse(mRemoteInputQuickSettingsDisabler.mRemoteInputActive); - verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean()); - } - - @Test - public void shouldDisableQuickSetting_afteActiviate() { - mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE); - mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE); - assertTrue(mRemoteInputQuickSettingsDisabler.mRemoteInputActive); - verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean()); - } - - @Test - public void testChangeToLandscape() { - Configuration c = new Configuration(mContext.getResources().getConfiguration()); - c.orientation = Configuration.ORIENTATION_PORTRAIT; - mRemoteInputQuickSettingsDisabler.onConfigChanged(c); - c.orientation = Configuration.ORIENTATION_LANDSCAPE; - mRemoteInputQuickSettingsDisabler.onConfigChanged(c); - assertTrue(mRemoteInputQuickSettingsDisabler.misLandscape); - verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean()); - } - - @Test - public void testChangeToPortrait() { - Configuration c = new Configuration(mContext.getResources().getConfiguration()); - c.orientation = Configuration.ORIENTATION_LANDSCAPE; - mRemoteInputQuickSettingsDisabler.onConfigChanged(c); - c.orientation = Configuration.ORIENTATION_PORTRAIT; - mRemoteInputQuickSettingsDisabler.onConfigChanged(c); - assertFalse(mRemoteInputQuickSettingsDisabler.misLandscape); - verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean()); - } - -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt new file mode 100644 index 000000000000..1ab0582a6c5b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt @@ -0,0 +1,135 @@ +/* + * 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.statusbar.policy + +import android.app.StatusBarManager +import android.content.res.Configuration +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest + +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.CommandQueue +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() { + + @Mock lateinit var commandQueue: CommandQueue + private lateinit var remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler + private lateinit var configuration: Configuration + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + remoteInputQuickSettingsDisabler = RemoteInputQuickSettingsDisabler( + mContext, + commandQueue, Mockito.mock(ConfigurationController::class.java) + ) + configuration = Configuration(mContext.resources.configuration) + + // Default these conditions to what they need to be to disable QS. + mContext.orCreateTestableResources + .addOverride(R.bool.config_use_split_notification_shade, /* value= */false) + remoteInputQuickSettingsDisabler.setRemoteInputActive(true) + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + remoteInputQuickSettingsDisabler.onConfigChanged(configuration) + } + + @Test + fun whenRemoteInputActiveAndLandscapeAndNotSplitShade_shouldDisableQs() { + assertThat( + shouldDisableQs( + remoteInputQuickSettingsDisabler.adjustDisableFlags(0) + ) + ) + .isTrue() + } + + @Test + fun whenRemoteInputNotActive_shouldNotDisableQs() { + remoteInputQuickSettingsDisabler.setRemoteInputActive(false) + + assertThat( + shouldDisableQs( + remoteInputQuickSettingsDisabler.adjustDisableFlags(0) + ) + ) + .isFalse() + } + + @Test + fun whenSplitShadeEnabled_shouldNotDisableQs() { + mContext.orCreateTestableResources + .addOverride(R.bool.config_use_split_notification_shade, /* value= */true) + remoteInputQuickSettingsDisabler.onConfigChanged(configuration) + + assertThat( + shouldDisableQs( + remoteInputQuickSettingsDisabler.adjustDisableFlags(0) + ) + ) + .isFalse() + } + + @Test + fun whenPortrait_shouldNotDisableQs() { + configuration.orientation = Configuration.ORIENTATION_PORTRAIT + remoteInputQuickSettingsDisabler.onConfigChanged(configuration) + + assertThat( + shouldDisableQs( + remoteInputQuickSettingsDisabler.adjustDisableFlags(0) + ) + ) + .isFalse() + } + + @Test + fun whenRemoteInputChanges_recomputeTriggered() { + remoteInputQuickSettingsDisabler.setRemoteInputActive(false) + + verify(commandQueue, atLeastOnce()).recomputeDisableFlags( + anyInt(), anyBoolean() + ) + } + + @Test + fun whenConfigChanges_recomputeTriggered() { + configuration.orientation = Configuration.ORIENTATION_PORTRAIT + remoteInputQuickSettingsDisabler.onConfigChanged(configuration) + + verify(commandQueue, atLeastOnce()).recomputeDisableFlags( + anyInt(), anyBoolean() + ) + } + + private fun shouldDisableQs(state: Int): Boolean { + return state and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0 + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt new file mode 100644 index 000000000000..db7a85166807 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.unfold.util + +import android.animation.ValueAnimator +import android.content.ContentResolver +import android.database.ContentObserver +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.util.mockito.any +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() { + + @Mock + lateinit var contentResolver: ContentResolver + + @Mock + lateinit var sourceProvider: UnfoldTransitionProgressProvider + + @Mock + lateinit var sinkProvider: TransitionProgressListener + + lateinit var progressProvider: ScaleAwareTransitionProgressProvider + + private val sourceProviderListenerCaptor = + ArgumentCaptor.forClass(TransitionProgressListener::class.java) + + private val animatorDurationScaleListenerCaptor = + ArgumentCaptor.forClass(ContentObserver::class.java) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + progressProvider = ScaleAwareTransitionProgressProvider( + sourceProvider, + contentResolver + ) + + verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture()) + verify(contentResolver).registerContentObserver(any(), any(), + animatorDurationScaleListenerCaptor.capture()) + + progressProvider.addCallback(sinkProvider) + } + + @Test + fun onTransitionStarted_animationsEnabled_eventReceived() { + setAnimationsEnabled(true) + + source.onTransitionStarted() + + verify(sinkProvider).onTransitionStarted() + } + + @Test + fun onTransitionStarted_animationsNotEnabled_eventNotReceived() { + setAnimationsEnabled(false) + + source.onTransitionStarted() + + verifyNoMoreInteractions(sinkProvider) + } + + @Test + fun onTransitionEnd_animationsEnabled_eventReceived() { + setAnimationsEnabled(true) + + source.onTransitionFinished() + + verify(sinkProvider).onTransitionFinished() + } + + @Test + fun onTransitionEnd_animationsNotEnabled_eventNotReceived() { + setAnimationsEnabled(false) + + source.onTransitionFinished() + + verifyNoMoreInteractions(sinkProvider) + } + + @Test + fun onTransitionProgress_animationsEnabled_eventReceived() { + setAnimationsEnabled(true) + + source.onTransitionProgress(42f) + + verify(sinkProvider).onTransitionProgress(42f) + } + + @Test + fun onTransitionProgress_animationsNotEnabled_eventNotReceived() { + setAnimationsEnabled(false) + + source.onTransitionProgress(42f) + + verifyNoMoreInteractions(sinkProvider) + } + + private fun setAnimationsEnabled(enabled: Boolean) { + val durationScale = if (enabled) { + 1f + } else { + 0f + } + ValueAnimator.setDurationScale(durationScale) + animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false) + } + + private val source: TransitionProgressListener + get() = sourceProviderListenerCaptor.value +} diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 9ee0159e903a..0b95fefec103 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -4068,14 +4068,20 @@ public class UserBackupManagerService { } int operationType; + TransportClient transportClient = null; try { - operationType = getOperationTypeFromTransport( - mTransportManager.getTransportClientOrThrow(transport, /* caller */ - "BMS.beginRestoreSession")); + transportClient = mTransportManager.getTransportClientOrThrow( + transport, /* caller */"BMS.beginRestoreSession"); + operationType = getOperationTypeFromTransport(transportClient); } catch (TransportNotAvailableException | TransportNotRegisteredException | RemoteException e) { Slog.w(TAG, "Failed to get operation type from transport: " + e); return null; + } finally { + if (transportClient != null) { + mTransportManager.disposeOfTransportClient(transportClient, + /* caller */"BMS.beginRestoreSession"); + } } synchronized (this) { diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 6a26bea6f8f0..b47ea4f7a4b8 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -551,9 +551,9 @@ public class SpatializerHelper { logd("canBeSpatialized false due to usage:" + attributes.getUsage()); return false; } - AudioDeviceAttributes[] devices = - // going through adapter to take advantage of routing cache - (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray(); + AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1]; + // going through adapter to take advantage of routing cache + mASA.getDevicesForAttributes(attributes).toArray(devices); final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices); logd("canBeSpatialized returning " + able); return able; diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index f42870b4b734..758cf7a7d430 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1036,7 +1036,8 @@ public class BiometricService extends SystemService { promptInfo.setAuthenticators(authenticators); return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, - userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */); + userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */, + getContext()); } /** @@ -1375,7 +1376,8 @@ public class BiometricService extends SystemService { try { final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, - opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists()); + opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(), + getContext()); final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus(); @@ -1383,8 +1385,11 @@ public class BiometricService extends SystemService { + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: " + promptInfo.isIgnoreEnrollmentState()); - - if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { + // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can + // be shown for this case. + if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS + || preAuthStatus.second + == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) { // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but // CREDENTIAL is requested and available, set the bundle to only request // CREDENTIAL. diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index a5a3542f49c7..05c3f68f355b 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -26,6 +26,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; +import android.content.Context; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.PromptInfo; @@ -59,6 +61,7 @@ class PreAuthInfo { static final int CREDENTIAL_NOT_ENROLLED = 9; static final int BIOMETRIC_LOCKOUT_TIMED = 10; static final int BIOMETRIC_LOCKOUT_PERMANENT = 11; + static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12; @IntDef({AUTHENTICATOR_OK, BIOMETRIC_NO_HARDWARE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY, @@ -69,7 +72,8 @@ class PreAuthInfo { BIOMETRIC_NOT_ENABLED_FOR_APPS, CREDENTIAL_NOT_ENROLLED, BIOMETRIC_LOCKOUT_TIMED, - BIOMETRIC_LOCKOUT_PERMANENT}) + BIOMETRIC_LOCKOUT_PERMANENT, + BIOMETRIC_SENSOR_PRIVACY_ENABLED}) @Retention(RetentionPolicy.SOURCE) @interface AuthenticatorStatus {} @@ -84,13 +88,15 @@ class PreAuthInfo { final boolean credentialAvailable; final boolean confirmationRequested; final boolean ignoreEnrollmentState; + final int userId; + final Context context; static PreAuthInfo create(ITrustManager trustManager, DevicePolicyManager devicePolicyManager, BiometricService.SettingObserver settingObserver, List<BiometricSensor> sensors, int userId, PromptInfo promptInfo, String opPackageName, - boolean checkDevicePolicyManager) + boolean checkDevicePolicyManager, Context context) throws RemoteException { final boolean confirmationRequested = promptInfo.isConfirmationRequested(); @@ -116,14 +122,22 @@ class PreAuthInfo { devicePolicyManager, settingObserver, sensor, userId, opPackageName, checkDevicePolicyManager, requestedStrength, promptInfo.getAllowedSensorIds(), - promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState(), + context); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id + " Modality: " + sensor.modality + " Status: " + status); - if (status == AUTHENTICATOR_OK) { + // A sensor with privacy enabled will still be eligible to + // authenticate with biometric prompt. This is so the framework can display + // a sensor privacy error message to users after briefly showing the + // Biometric Prompt. + // + // Note: if only a certain sensor is required and the privacy is enabled, + // canAuthenticate() will return false. + if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) { eligibleSensors.add(sensor); } else { ineligibleSensors.add(new Pair<>(sensor, status)); @@ -133,7 +147,7 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, - promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState(), userId, context); } /** @@ -149,7 +163,7 @@ class PreAuthInfo { BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, @NonNull List<Integer> requestedSensorIds, - boolean ignoreEnrollmentState) { + boolean ignoreEnrollmentState, Context context) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { return BIOMETRIC_NO_HARDWARE; @@ -175,6 +189,16 @@ class PreAuthInfo { && !ignoreEnrollmentState) { return BIOMETRIC_NOT_ENROLLED; } + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) { + if (sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) { + return BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } + } + final @LockoutTracker.LockoutMode int lockoutMode = sensor.impl.getLockoutModeForUser(userId); @@ -243,7 +267,8 @@ class PreAuthInfo { private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, - boolean confirmationRequested, boolean ignoreEnrollmentState) { + boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, + Context context) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; this.credentialRequested = credentialRequested; @@ -253,6 +278,8 @@ class PreAuthInfo { this.credentialAvailable = credentialAvailable; this.confirmationRequested = confirmationRequested; this.ignoreEnrollmentState = ignoreEnrollmentState; + this.userId = userId; + this.context = context; } private Pair<BiometricSensor, Integer> calculateErrorByPriority() { @@ -280,15 +307,35 @@ class PreAuthInfo { private Pair<Integer, Integer> getInternalStatus() { @AuthenticatorStatus final int status; @BiometricAuthenticator.Modality int modality = TYPE_NONE; + + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + boolean cameraPrivacyEnabled = false; + if (sensorPrivacyManager != null) { + cameraPrivacyEnabled = sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId); + } + if (mBiometricRequested && credentialRequested) { if (credentialAvailable || !eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - if (credentialAvailable) { - modality |= TYPE_CREDENTIAL; - } for (BiometricSensor sensor : eligibleSensors) { modality |= sensor.modality; } + + if (credentialAvailable) { + modality |= TYPE_CREDENTIAL; + status = AUTHENTICATOR_OK; + } else if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face, credential is unavailable, + // and the face sensor privacy is enabled then return + // BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -302,10 +349,18 @@ class PreAuthInfo { } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - for (BiometricSensor sensor : eligibleSensors) { - modality |= sensor.modality; - } + for (BiometricSensor sensor : eligibleSensors) { + modality |= sensor.modality; + } + if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face and the privacy is enabled + // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -326,9 +381,9 @@ class PreAuthInfo { Slog.e(TAG, "No authenticators requested"); status = BIOMETRIC_NO_HARDWARE; } - Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality + " AuthenticatorStatus: " + status); + return new Pair<>(modality, status); } @@ -362,6 +417,7 @@ class PreAuthInfo { case CREDENTIAL_NOT_ENROLLED: case BIOMETRIC_LOCKOUT_TIMED: case BIOMETRIC_LOCKOUT_PERMANENT: + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: break; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 4f7c6b012c23..0e2582c23b86 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -33,6 +33,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_TIMED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; +import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; import android.annotation.NonNull; @@ -278,6 +279,9 @@ public class Utils { case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; break; + case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; @@ -337,7 +341,8 @@ public class Utils { case BIOMETRIC_LOCKOUT_PERMANENT: return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; - + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: + return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_NOT_ENABLED_FOR_APPS: diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 97d791b7e1c9..4131ae127ab2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -56,6 +57,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @NonNull private final LockoutCache mLockoutCache; @Nullable private final NotificationManager mNotificationManager; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; private final int[] mBiometricPromptIgnoreList; private final int[] mBiometricPromptIgnoreListVendor; @@ -81,6 +83,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mUsageStats = usageStats; mLockoutCache = lockoutCache; mNotificationManager = context.getSystemService(NotificationManager.class); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -108,7 +111,16 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Override protected void startHalOperation() { try { - mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } else { + mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index 2ef0911658b1..2158dfe7bde5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -19,6 +19,8 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.ISession; @@ -41,6 +43,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det private final boolean mIsStrongBiometric; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId, @@ -51,6 +54,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } @Override @@ -73,6 +77,14 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void startHalOperation() { + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { mCancellationSignal = getFreshDaemon().detectInteraction(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 40f2801541d3..7548d2871a15 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -55,6 +56,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { private final int[] mKeyguardIgnoreListVendor; private int mLastAcquire; + private SensorPrivacyManager mSensorPrivacyManager; FaceAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @@ -71,6 +73,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -97,6 +100,15 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @Override protected void startHalOperation() { + + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3fb5f02b756a..1516739de99f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2830,7 +2830,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken); - if (targetWindow != null && mLastImeTargetWindow != targetWindow) { + if (targetWindow != null) { mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow); } mLastImeTargetWindow = targetWindow; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ba11e9c26dfe..7a70bfe91248 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24299,24 +24299,24 @@ public class PackageManagerService extends IPackageManager.Stub } enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, true /* checkShell */, "stop package"); - boolean shouldUnhibernate = false; // writer synchronized (mLock) { final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.getStopped(userId) && !stopped) { - shouldUnhibernate = true; - } if (!shouldFilterApplicationLocked(ps, callingUid, userId) && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) { scheduleWritePackageRestrictionsLocked(userId); } } - if (shouldUnhibernate) { + // If this would cause the app to leave force-stop, then also make sure to unhibernate the + // app if needed. + if (!stopped) { mHandler.post(() -> { AppHibernationManagerInternal ah = mInjector.getLocalService(AppHibernationManagerInternal.class); - ah.setHibernatingForUser(packageName, userId, false); - ah.setHibernatingGlobally(packageName, false); + if (ah != null && ah.isHibernatingForUser(packageName, userId)) { + ah.setHibernatingForUser(packageName, userId, false); + ah.setHibernatingGlobally(packageName, false); + } }); } } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 2be29d4a5ab4..dfff76d3a044 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -50,6 +50,10 @@ import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import static android.util.MathUtils.constrain; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; @@ -4387,8 +4391,9 @@ public class StatsPullAtomService extends SystemService { final int userId = userInfo.getUserHandle().getIdentifier(); if (isAccessibilityShortcutUser(mContext, userId)) { - final int software_shortcut_type = Settings.Secure.getIntForUser(resolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId); + final int software_shortcut_type = convertToAccessibilityShortcutType( + Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId)); final String software_shortcut_list = Settings.Secure.getStringForUser(resolver, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId); final int software_shortcut_service_num = countAccessibilityServices( @@ -4509,6 +4514,19 @@ public class StatsPullAtomService extends SystemService { && !TextUtils.isEmpty(software_string); } + private int convertToAccessibilityShortcutType(int shortcutType) { + switch (shortcutType) { + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; + default: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; + } + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 7dec4e785f5c..51c3b33b0e79 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -77,6 +77,7 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.SystemClock; +import android.provider.Settings; import android.util.ArraySet; import android.util.Slog; @@ -782,8 +783,19 @@ public class VcnGatewayConnection extends StateMachine { // TODO(b/179091925): Move the delayed-message handling to BaseState // If underlying is null, all underlying networks have been lost. Disconnect VCN after a - // timeout. + // timeout (or immediately if in airplane mode, since the device user has indicated that + // the radios should all be turned off). if (underlying == null) { + if (mDeps.isAirplaneModeOn(mVcnContext)) { + sendMessageAndAcquireWakeLock( + EVENT_UNDERLYING_NETWORK_CHANGED, + TOKEN_ALL, + new EventUnderlyingNetworkChangedInfo(null)); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */); + return; + } + setDisconnectRequestAlarm(); } else { // Received a new Network so any previous alarm is irrelevant - cancel + clear it, @@ -2414,6 +2426,12 @@ public class VcnGatewayConnection extends StateMachine { validationStatusCallback); } + /** Checks if airplane mode is enabled. */ + public boolean isAirplaneModeOn(@NonNull VcnContext vcnContext) { + return Settings.Global.getInt(vcnContext.getContext().getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + /** Gets the elapsed real time since boot, in millis. */ public long getElapsedRealTime() { return SystemClock.elapsedRealtime(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index acb155bac5bc..b469a80a3a2f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1057,6 +1057,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" forceNewConfig="); pw.println(forceNewConfig); pw.print(prefix); pw.print("mActivityType="); pw.println(activityTypeToString(getActivityType())); + pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput="); + pw.println(mImeInsetsFrozenUntilStartInput); if (requestedVrComponent != null) { pw.print(prefix); pw.print("requestedVrComponent="); @@ -6563,6 +6565,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) { return false; } + // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is + // not specified in the ActivityOptions. + if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME) { + return false; + } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { + return true; + } } if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); @@ -6572,14 +6581,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return sourceRecord.mSplashScreenStyleEmpty; } - // If this activity was launched from a system surface, never use an empty splash screen + // If this activity was launched from Launcher or System for first start, never use an + // empty splash screen. // Need to check sourceRecord before in case this activity is launched from service. - if (launchedFromSystemSurface()) { - return false; - } - - // Otherwise use empty. - return true; + return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM + || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME); } private int getSplashscreenTheme() { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b7c992e073f3..35e9c8fc258c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4130,11 +4130,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * which controls the visibility and animation of the input method window. */ void updateImeInputAndControlTarget(WindowState target) { + if (target != null && target.mActivityRecord != null) { + target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; + } if (mImeInputTarget != target) { ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target); - if (target != null && target.mActivityRecord != null) { - target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; - } setImeInputTarget(target); mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a53a8cdea0a7..a4808bc64467 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1451,11 +1451,11 @@ class Task extends TaskFragment { } /** Called when an {@link ActivityRecord} is added as a descendant */ - void onDescendantActivityAdded(boolean hadChild, int activityType, ActivityRecord r) { + void onDescendantActivityAdded(boolean hadActivity, int activityType, ActivityRecord r) { warnForNonLeafTask("onDescendantActivityAdded"); // Only set this based on the first activity - if (!hadChild) { + if (!hadActivity) { if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) { // Normally non-standard activity type for the activity record will be set when the // object is created, however we delay setting the standard application type until diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 985128aaec35..7bcf8a9f274f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -102,6 +102,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -1670,8 +1671,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { boolean isAddingActivity = child.asActivityRecord() != null; final Task task = isAddingActivity ? getTask() : null; - // If this task had any child before we added this one. - boolean taskHadChild = task != null && task.hasChild(); + // If this task had any activity before we added this one. + boolean taskHadActivity = task != null && task.getActivity(Objects::nonNull) != null; // getActivityType() looks at the top child, so we need to read the type before adding // a new child in case the new child is on top and UNDEFINED. final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED; @@ -1680,7 +1681,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (isAddingActivity && task != null) { child.asActivityRecord().inHistory = true; - task.onDescendantActivityAdded(taskHadChild, activityType, child.asActivityRecord()); + task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord()); } } @@ -1957,7 +1958,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { - if (WindowConfiguration.isFloating(windowingMode)) { + // When entering to or exiting from Pip, the PipTaskOrganizer will set the + // windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and + // temporarily set the bounds of the task to fullscreen size for transitioning. + // It will get the wrong value if the calculation is based on this temporary + // fullscreen bounds. + // We should just inherit the value from parent for this temporary state. + final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED + && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds); + if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) { // For floating tasks, calculate the smallest width from the bounds of the task inOutConfig.smallestScreenWidthDp = (int) ( Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density); @@ -2182,14 +2191,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { TaskFragmentInfo getTaskFragmentInfo() { List<IBinder> childActivities = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { - WindowContainer wc = getChildAt(i); - if (mTaskFragmentOrganizerUid != INVALID_UID - && wc.asActivityRecord() != null - && wc.asActivityRecord().info.processName.equals( - mTaskFragmentOrganizerProcessName) - && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) { + final WindowContainer wc = getChildAt(i); + final ActivityRecord ar = wc.asActivityRecord(); + if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null + && ar.info.processName.equals(mTaskFragmentOrganizerProcessName) + && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) { // Only includes Activities that belong to the organizer process for security. - childActivities.add(wc.asActivityRecord().appToken); + childActivities.add(ar.appToken); } } final Point positionInParent = new Point(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt index fd97557480bb..e053dc3fd32e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt @@ -29,6 +29,7 @@ import com.android.server.apphibernation.AppHibernationManagerInternal import com.android.server.apphibernation.AppHibernationService import com.android.server.extendedtestutils.wheneverStatic import com.android.server.testutils.whenever +import org.junit.Assert import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -100,8 +101,11 @@ class PackageManagerServiceHibernationTests { rule.system().dataAppDirectory) val pm = createPackageManagerService() rule.system().validateFinalState() - val ps = pm.getPackageSetting(TEST_PACKAGE_NAME) - ps!!.setStopped(true, TEST_USER_ID) + + TestableLooper.get(this).processAllMessages() + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) @@ -112,6 +116,31 @@ class PackageManagerServiceHibernationTests { } @Test + fun testExitForceStop_nonExistingAppHibernationManager_doesNotThrowException() { + whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java)) + .thenReturn(null) + + rule.system().stageScanExistingPackage( + TEST_PACKAGE_NAME, + 1L, + rule.system().dataAppDirectory) + val pm = createPackageManagerService() + rule.system().validateFinalState() + + TestableLooper.get(this).processAllMessages() + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) + + try { + pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) + TestableLooper.get(this).processAllMessages() + } catch (e: Exception) { + Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e") + } + } + + @Test fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() { rule.system().stageScanExistingPackage( TEST_PACKAGE_NAME, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index b3f7587df612..b255a35c512e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -302,6 +302,65 @@ public class AuthSessionTest { testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null)); } + // TODO (b/208484275) : Enable these tests + // @Test + // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(false); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED, + // preAuthInfo.getCanAuthenticateResult()); + // // Even though canAuth returns privacy enabled, we should still be able to authenticate. + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = + // createPromptInfo(Authenticators.BIOMETRIC_STRONG + // | Authenticators. DEVICE_CREDENTIAL); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException { final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class); @@ -331,7 +390,8 @@ public class AuthSessionTest { userId, promptInfo, TEST_PACKAGE, - checkDevicePolicyManager); + checkDevicePolicyManager, + mContext); } private AuthSession createAuthSession(List<BiometricSensor> sensors, diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 707e463424ae..9a68b5f1b609 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -39,6 +39,7 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.os.Process.NOBODY_UID; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.InsetsState.ITYPE_IME; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -128,6 +129,8 @@ import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner.Stub; import android.view.IWindowManager; import android.view.IWindowSession; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; @@ -3038,6 +3041,41 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); } + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + + InsetsSource imeSource = new InsetsSource(ITYPE_IME); + app.getInsetsState().addSource(imeSource); + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.updateImeInputAndControlTarget(app); + + InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app); + assertFalse(state.getSource(ITYPE_IME).isVisible()); + assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty()); + + // Simulate app is closing and expect IME insets is frozen. + mDisplayContent.mOpeningApps.clear(); + app.mActivityRecord.commitVisibility(false, false); + app.mActivityRecord.onWindowsGone(); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Simulate app re-start input or turning screen off/on then unlocked by un-secure + // keyguard to back to the app, expect IME insets is not frozen + imeSource.setFrame(new Rect(100, 400, 500, 500)); + app.getInsetsState().addSource(imeSource); + app.getInsetsState().setSourceVisible(ITYPE_IME, true); + mDisplayContent.updateImeInputAndControlTarget(app); + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Verify when IME is visible and the app can receive the right IME insets from policy. + makeWindowVisibleAndDrawn(app, mImeWindow); + state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app); + assertTrue(state.getSource(ITYPE_IME).isVisible()); + assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame()); + } + @Test public void testInClosingAnimation_doNotHideSurface() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index c9a8947ab5ef..7b5f0b180c6e 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -118,6 +118,8 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { + doReturn(false).when(mDeps).isAirplaneModeOn(any()); + mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(null); @@ -129,6 +131,19 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testNullNetworkAirplaneModeDisconnects() throws Exception { + doReturn(true).when(mDeps).isAirplaneModeOn(any()); + + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + verify(mIkeSession).kill(); + } + + @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() |