Merge "TaskFragment without running activity cannot occlude others" into sc-v2-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index ec8dce0..8ba8c1a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1298,6 +1298,7 @@
field public static final int shortcutLongLabel = 16844074; // 0x101052a
field public static final int shortcutShortLabel = 16844073; // 0x1010529
field public static final int shouldDisableView = 16843246; // 0x10101ee
+ field public static final int shouldUseDefaultUnfoldTransition;
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
@@ -6926,6 +6927,7 @@
method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
method public CharSequence loadLabel(android.content.pm.PackageManager);
method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager);
+ method public boolean shouldUseDefaultUnfoldTransition();
method public boolean supportsMultipleDisplays();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR;
@@ -46890,8 +46892,15 @@
}
@UiThread public interface AttachedSurfaceControl {
+ method public default void addOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener);
method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction);
method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
+ method public default int getSurfaceTransformHint();
+ method public default void removeOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener);
+ }
+
+ @UiThread public static interface AttachedSurfaceControl.OnSurfaceTransformHintChangedListener {
+ method public void onSurfaceTransformHintChanged(int);
}
public final class Choreographer {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9c01bca..1d406a5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5379,12 +5379,14 @@
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void getEffectParameter(int, @NonNull byte[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeOnHeadTrackingModeChangedListener(@NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setDesiredHeadTrackingMode(int);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEffectParameter(int, @NonNull byte[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener);
@@ -14448,6 +14450,7 @@
method public int getFlags();
method @Nullable public android.view.contentcapture.ContentCaptureSessionId getParentSessionId();
method public int getTaskId();
+ method @Nullable public android.os.IBinder getWindowToken();
field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1
field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2
field public static final int FLAG_RECONNECTED = 4; // 0x4
@@ -14455,6 +14458,7 @@
public final class ContentCaptureEvent implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.graphics.Rect getBounds();
method @Nullable public android.view.contentcapture.ContentCaptureContext getContentCaptureContext();
method public long getEventTime();
method @Nullable public android.view.autofill.AutofillId getId();
@@ -14474,6 +14478,7 @@
field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3
field public static final int TYPE_VIEW_TREE_APPEARED = 5; // 0x5
field public static final int TYPE_VIEW_TREE_APPEARING = 4; // 0x4
+ field public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10; // 0xa
}
public final class ContentCaptureManager {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2e22b92..80554d7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3616,6 +3616,11 @@
}
activity.mLaunchedFromBubble = r.mLaunchedFromBubble;
activity.mCalled = false;
+ // Assigning the activity to the record before calling onCreate() allows
+ // ActivityThread#getActivity() lookup for the callbacks triggered from
+ // ActivityLifecycleCallbacks#onActivityCreated() or
+ // ActivityLifecycleCallback#onActivityPostCreated().
+ r.activity = activity;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
@@ -3626,7 +3631,6 @@
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
- r.activity = activity;
mLastReportedWindowingMode.put(activity.getActivityToken(),
config.windowConfiguration.getWindowingMode());
}
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index e9b0175..4dff4e0 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -81,6 +81,7 @@
final int mContextDescriptionResource;
final boolean mShowMetadataInPreview;
final boolean mSupportsAmbientMode;
+ final boolean mShouldUseDefaultUnfoldTransition;
final String mSettingsSliceUri;
final boolean mSupportMultipleDisplays;
@@ -145,6 +146,9 @@
mSupportsAmbientMode = sa.getBoolean(
com.android.internal.R.styleable.Wallpaper_supportsAmbientMode,
false);
+ mShouldUseDefaultUnfoldTransition = sa.getBoolean(
+ com.android.internal.R.styleable.Wallpaper_shouldUseDefaultUnfoldTransition,
+ true);
mSettingsSliceUri = sa.getString(
com.android.internal.R.styleable.Wallpaper_settingsSliceUri);
mSupportMultipleDisplays = sa.getBoolean(
@@ -171,6 +175,7 @@
mSupportsAmbientMode = source.readInt() != 0;
mSettingsSliceUri = source.readString();
mSupportMultipleDisplays = source.readInt() != 0;
+ mShouldUseDefaultUnfoldTransition = source.readInt() != 0;
mService = ResolveInfo.CREATOR.createFromParcel(source);
}
@@ -393,6 +398,26 @@
return mSupportMultipleDisplays;
}
+ /**
+ * Returns whether this wallpaper should receive default zooming updates when unfolding.
+ * If set to false the wallpaper will not receive zoom events when folding or unfolding
+ * a foldable device, so it can implement its own unfold transition.
+ * <p>
+ * This corresponds to the value {@link
+ * android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition} in the XML description
+ * of the wallpaper.
+ * <p>
+ * The default value is {@code true}.
+ *
+ * @see android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition
+ * @return {@code true} if wallpaper should receive default fold/unfold transition updates
+ *
+ * @attr ref android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition
+ */
+ public boolean shouldUseDefaultUnfoldTransition() {
+ return mShouldUseDefaultUnfoldTransition;
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "Service:");
mService.dump(pw, prefix + " ");
@@ -423,6 +448,7 @@
dest.writeInt(mSupportsAmbientMode ? 1 : 0);
dest.writeString(mSettingsSliceUri);
dest.writeInt(mSupportMultipleDisplays ? 1 : 0);
+ dest.writeInt(mShouldUseDefaultUnfoldTransition ? 1 : 0);
mService.writeToParcel(dest, flags);
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java
new file mode 100644
index 0000000..4c91c16
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java
@@ -0,0 +1,28 @@
+/*
+ * 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.hardware.devicestate;
+
+/**
+ * Device state manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class DeviceStateManagerInternal {
+
+ /** Returns the list of currently supported device state identifiers. */
+ public abstract int[] getSupportedStateIdentifiers();
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 4f20553..5bb51c1 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -35,6 +35,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Display manager local system service interface.
@@ -129,6 +130,14 @@
public abstract DisplayInfo getDisplayInfo(int displayId);
/**
+ * Returns a set of DisplayInfo, for the states that may be assumed by either the given display,
+ * or any other display within that display's group.
+ *
+ * @param displayId The logical display id to fetch DisplayInfo for.
+ */
+ public abstract Set<DisplayInfo> getPossibleDisplayInfo(int displayId);
+
+ /**
* Returns the position of the display's projection.
*
* @param displayId The logical display id.
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index d48d562..a3d595c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -146,6 +146,7 @@
private CryptoObject mCryptoObject;
@Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
+ @Nullable private float[] mEnrollStageThresholds;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
@@ -1329,6 +1330,46 @@
/**
* @hide
*/
+ public int getEnrollStageCount() {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+ return mEnrollStageThresholds.length + 1;
+ }
+
+ /**
+ * @hide
+ */
+ public float getEnrollStageThreshold(int index) {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+
+ if (index < 0 || index > mEnrollStageThresholds.length) {
+ Slog.w(TAG, "Unsupported enroll stage index: " + index);
+ return index < 0 ? 0f : 1f;
+ }
+
+ // The implicit threshold for the final stage is always 1.
+ return index == mEnrollStageThresholds.length ? 1f : mEnrollStageThresholds[index];
+ }
+
+ @NonNull
+ private static float[] createEnrollStageThresholds(@NonNull Context context) {
+ // TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config
+ final String[] enrollStageThresholdStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.config_udfps_enroll_stage_thresholds);
+
+ final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length];
+ for (int i = 0; i < enrollStageThresholds.length; i++) {
+ enrollStageThresholds[i] = Float.parseFloat(enrollStageThresholdStrings[i]);
+ }
+ return enrollStageThresholds;
+ }
+
+ /**
+ * @hide
+ */
public static String getErrorString(Context context, int errMsg, int vendorCode) {
switch (errMsg) {
case FINGERPRINT_ERROR_HW_UNAVAILABLE:
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0899ef5..1bdfdc2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8966,6 +8966,15 @@
public static final String UNSAFE_VOLUME_MUSIC_ACTIVE_MS = "unsafe_volume_music_active_ms";
/**
+ * Indicates whether the spatial audio feature was enabled for this user.
+ *
+ * Type : int (0 disabled, 1 enabled)
+ *
+ * @hide
+ */
+ public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled";
+
+ /**
* Indicates whether notification display on the lock screen is enabled.
* <p>
* Type: int (0 for false, 1 for true)
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index bcc5b56..b2fc9a0 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -53,4 +53,72 @@
* to the View hierarchy you may need to call {@link android.view.View#invalidate}
*/
boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t);
+
+ /**
+ * The transform hint can be used by a buffer producer to pre-rotate the rendering such that the
+ * final transformation in the system composer is identity. This can be very useful when used in
+ * conjunction with the h/w composer HAL in situations where it cannot handle rotations or
+ * handle them with an additional power cost.
+ *
+ * The transform hint should be used with ASurfaceControl APIs when submitting buffers.
+ * Example usage:
+ *
+ * 1. After a configuration change, before dequeuing a buffer, the buffer producer queries the
+ * function for the transform hint.
+ *
+ * 2. The desired buffer width and height is rotated by the transform hint.
+ *
+ * 3. The producer dequeues a buffer of the new pre-rotated size.
+ *
+ * 4. The producer renders to the buffer such that the image is already transformed, that is
+ * applying the transform hint to the rendering.
+ *
+ * 5. The producer applies the inverse transform hint to the buffer it just rendered.
+ *
+ * 6. The producer queues the pre-transformed buffer with the buffer transform.
+ *
+ * 7. The composer combines the buffer transform with the display transform. If the buffer
+ * transform happens to cancel out the display transform then no rotation is needed and there
+ * will be no performance penalties.
+ *
+ * Note, when using ANativeWindow APIs in conjunction with a NativeActivity Surface or
+ * SurfaceView Surface, the buffer producer will already have access to the transform hint and
+ * no additional work is needed.
+ */
+ default @Surface.Rotation int getSurfaceTransformHint() {
+ return Surface.ROTATION_0;
+ }
+
+ /**
+ * Surface transform hint change listener.
+ * @see #getSurfaceTransformHint
+ */
+ @UiThread
+ interface OnSurfaceTransformHintChangedListener {
+ /**
+ * @param hint new surface transform hint
+ * @see #getSurfaceTransformHint
+ */
+ void onSurfaceTransformHintChanged(@Surface.Rotation int hint);
+ }
+
+ /**
+ * Registers a surface transform hint changed listener to receive notifications about when
+ * the transform hint changes.
+ *
+ * @see #getSurfaceTransformHint
+ * @see #removeOnSurfaceTransformHintChangedListener
+ */
+ default void addOnSurfaceTransformHintChangedListener(
+ @NonNull OnSurfaceTransformHintChangedListener listener) {
+ }
+
+ /**
+ * Unregisters a surface transform hint changed listener.
+ *
+ * @see #addOnSurfaceTransformHintChangedListener
+ */
+ default void removeOnSurfaceTransformHintChangedListener(
+ @NonNull OnSurfaceTransformHintChangedListener listener) {
+ }
}
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 3f71d4d..046232a 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -208,6 +208,15 @@
*/
public final @WindowManager.LayoutParams.WindowType int windowType;
+ /**
+ * {@code true} if its parent is also a {@link RemoteAnimationTarget} in the same transition.
+ *
+ * For example, when a TaskFragment is resizing while one of its children is open/close, both
+ * windows will be animation targets. This value will be {@code true} for the child, so that
+ * the handler can choose to handle it differently.
+ */
+ public boolean hasAnimatingParent;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
@@ -265,6 +274,7 @@
taskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
allowEnterPip = in.readBoolean();
windowType = in.readInt();
+ hasAnimatingParent = in.readBoolean();
}
@Override
@@ -292,6 +302,7 @@
dest.writeTypedObject(taskInfo, 0 /* flags */);
dest.writeBoolean(allowEnterPip);
dest.writeInt(windowType);
+ dest.writeBoolean(hasAnimatingParent);
}
public void dump(PrintWriter pw, String prefix) {
@@ -311,6 +322,7 @@
pw.print(prefix); pw.print("taskInfo="); pw.println(taskInfo);
pw.print(prefix); pw.print("allowEnterPip="); pw.println(allowEnterPip);
pw.print(prefix); pw.print("windowType="); pw.print(windowType);
+ pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d6186d7..5b8dc40 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -41,6 +41,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.gui.DropInputMode;
import android.hardware.HardwareBuffer;
import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayedContentSample;
@@ -150,13 +151,15 @@
float childRelativeTop, float childRelativeRight, float childRelativeBottom);
private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
boolean isTrustedOverlay);
-
+ private static native void nativeSetDropInputMode(
+ long transactionObj, long nativeObject, int flags);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
private static native boolean nativeClearAnimationFrameStats();
private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
private static native long[] nativeGetPhysicalDisplayIds();
+ private static native long nativeGetPrimaryPhysicalDisplayId();
private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
@@ -2277,6 +2280,15 @@
}
/**
+ * Exposed to identify the correct display to apply the primary display orientation. Avoid using
+ * for any other purpose.
+ * @hide
+ */
+ public static long getPrimaryPhysicalDisplayId() {
+ return nativeGetPrimaryPhysicalDisplayId();
+ }
+
+ /**
* @hide
*/
public static IBinder getPhysicalDisplayToken(long physicalDisplayId) {
@@ -3448,7 +3460,18 @@
return this;
}
- /**
+ /**
+ * Sets the input event drop mode on this SurfaceControl and its children. The caller must
+ * hold the ACCESS_SURFACE_FLINGER permission. See {@code InputEventDropMode}.
+ * @hide
+ */
+ public Transaction setDropInputMode(SurfaceControl sc, @DropInputMode int mode) {
+ checkPreconditions(sc);
+ nativeSetDropInputMode(mNativeObject, sc.mNativeObject, mode);
+ return this;
+ }
+
+ /**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
*
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 51910e0..60bb99d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1464,19 +1464,18 @@
@Override
public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
- if (mSurfaceControl == null) {
- return;
- }
-
- // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
- // its 2nd frame if RenderThread is running slowly could potentially see
- // this as false, enter the branch, get pre-empted, then this comes along
- // and reports a new position, then the UI thread resumes and reports
- // its position. This could therefore be de-sync'd in that interval, but
- // the synchronization would violate the rule that RT must never block
- // on the UI thread which would open up potential deadlocks. The risk of
- // a single-frame desync is therefore preferable for now.
synchronized(mSurfaceControlLock) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+ // its 2nd frame if RenderThread is running slowly could potentially see
+ // this as false, enter the branch, get pre-empted, then this comes along
+ // and reports a new position, then the UI thread resumes and reports
+ // its position. This could therefore be de-sync'd in that interval, but
+ // the synchronization would violate the rule that RT must never block
+ // on the UI thread which would open up potential deadlocks. The risk of
+ // a single-frame desync is therefore preferable for now.
mRtHandlingPositionUpdates = true;
}
if (mRTLastReportedPosition.left == left
@@ -1506,8 +1505,11 @@
if (mViewVisibility) {
mPositionChangedTransaction.show(mSurfaceControl);
}
- applyChildSurfaceTransaction_renderWorker(mPositionChangedTransaction,
- getViewRootImpl().mSurface, frameNumber);
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ applyChildSurfaceTransaction_renderWorker(mPositionChangedTransaction,
+ viewRoot.mSurface, frameNumber);
+ }
applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
mPendingTransaction = false;
} catch (Exception ex) {
@@ -1528,7 +1530,7 @@
@Override
public void positionLost(long frameNumber) {
- if (DEBUG) {
+ if (DEBUG_POSITION) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
}
@@ -1540,15 +1542,15 @@
mPositionChangedTransaction.clear();
mPendingTransaction = false;
}
- if (mSurfaceControl == null) {
- return;
- }
/**
* positionLost can be called while UI thread is un-paused so we
* need to hold the lock here.
*/
synchronized (mSurfaceControlLock) {
+ if (mSurfaceControl == null) {
+ return;
+ }
mRtTransaction.hide(mSurfaceControl);
if (mRtReleaseSurfaces) {
mRtReleaseSurfaces = false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4c36fc9..e38b883 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -312,6 +312,9 @@
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<>();
static boolean sFirstDrawComplete = false;
+ private ArrayList<OnSurfaceTransformHintChangedListener> mTransformHintListeners =
+ new ArrayList<>();
+ private @Surface.Rotation int mPreviousTransformHint = Surface.ROTATION_0;
/**
* Callback for notifying about global configuration changes.
*/
@@ -4273,6 +4276,14 @@
try {
if (!isContentCaptureEnabled()) return;
+ // Initial dispatch of window bounds to content capture
+ if (mAttachInfo.mContentCaptureManager != null) {
+ MainContentCaptureSession session =
+ mAttachInfo.mContentCaptureManager.getMainContentCaptureSession();
+ session.notifyWindowBoundsChanged(session.getId(),
+ getConfiguration().windowConfiguration.getBounds());
+ }
+
// Content capture is a go!
rootView.dispatchInitialProvideContentCaptureStructure();
} finally {
@@ -7804,6 +7815,14 @@
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
+
+ if (mAttachInfo.mContentCaptureManager != null) {
+ MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
+ .getMainContentCaptureSession();
+ mainSession.notifyWindowBoundsChanged(mainSession.getId(),
+ getConfiguration().windowConfiguration.getBounds());
+ }
+
mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
if (mSurfaceControl.isValid()) {
if (!useBLAST()) {
@@ -7824,6 +7843,11 @@
}
mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
}
+ int transformHint = mSurfaceControl.getTransformHint();
+ if (mPreviousTransformHint != transformHint) {
+ mPreviousTransformHint = transformHint;
+ dispatchTransformHintChanged(transformHint);
+ }
} else {
destroySurface();
}
@@ -10447,7 +10471,39 @@
return true;
}
- int getSurfaceTransformHint() {
+ @Override
+ public @Surface.Rotation int getSurfaceTransformHint() {
return mSurfaceControl.getTransformHint();
}
+
+ @Override
+ public void addOnSurfaceTransformHintChangedListener(
+ OnSurfaceTransformHintChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mTransformHintListeners.contains(listener)) {
+ throw new IllegalArgumentException(
+ "attempt to call addOnSurfaceTransformHintChangedListener() "
+ + "with a previously registered listener");
+ }
+ mTransformHintListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnSurfaceTransformHintChangedListener(
+ OnSurfaceTransformHintChangedListener listener) {
+ Objects.requireNonNull(listener);
+ mTransformHintListeners.remove(listener);
+ }
+
+ private void dispatchTransformHintChanged(@Surface.Rotation int hint) {
+ if (mTransformHintListeners.isEmpty()) {
+ return;
+ }
+ ArrayList<OnSurfaceTransformHintChangedListener> listeners =
+ (ArrayList<OnSurfaceTransformHintChangedListener>) mTransformHintListeners.clone();
+ for (int i = 0; i < listeners.size(); i++) {
+ OnSurfaceTransformHintChangedListener listener = listeners.get(i);
+ listener.onSurfaceTransformHintChanged(hint);
+ }
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 0fc6b08..7631269 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -374,8 +374,8 @@
currentDisplayInfo = possibleDisplayInfos.get(i);
// Calculate max bounds for this rotation and state.
- Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
- currentDisplayInfo.getNaturalHeight());
+ Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth,
+ currentDisplayInfo.logicalHeight);
// Calculate insets for the rotated max bounds.
// TODO(181127261) calculate insets for each display rotation and state.
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 9998fbc..0da54e5 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.LocusId;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Display;
@@ -105,6 +106,7 @@
private final int mFlags;
private final int mDisplayId;
private final ActivityId mActivityId;
+ private final IBinder mWindowToken;
// Fields below are set by the service upon "delivery" and are not marshalled in the parcel
private int mParentSessionId = NO_SESSION_ID;
@@ -112,7 +114,7 @@
/** @hide */
public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
@NonNull ActivityId activityId, @NonNull ComponentName componentName, int displayId,
- int flags) {
+ IBinder windowToken, int flags) {
if (clientContext != null) {
mHasClientContext = true;
mExtras = clientContext.mExtras;
@@ -126,6 +128,7 @@
mFlags = flags;
mDisplayId = displayId;
mActivityId = activityId;
+ mWindowToken = windowToken;
}
private ContentCaptureContext(@NonNull Builder builder) {
@@ -137,6 +140,7 @@
mFlags = 0;
mDisplayId = Display.INVALID_DISPLAY;
mActivityId = null;
+ mWindowToken = null;
}
/** @hide */
@@ -148,6 +152,7 @@
mFlags = original.mFlags | extraFlags;
mDisplayId = original.mDisplayId;
mActivityId = original.mActivityId;
+ mWindowToken = original.mWindowToken;
}
/**
@@ -230,6 +235,20 @@
}
/**
+ * Gets the window token of the activity associated with this context.
+ *
+ * <p>The token can be used to attach relevant overlay views to the activity's window. This can
+ * be done through {@link android.view.WindowManager.LayoutParams#token}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public IBinder getWindowToken() {
+ return mWindowToken;
+ }
+
+ /**
* Gets the flags associated with this context.
*
* @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE},
@@ -328,6 +347,7 @@
}
pw.print(", activityId="); pw.print(mActivityId);
pw.print(", displayId="); pw.print(mDisplayId);
+ pw.print(", windowToken="); pw.print(mWindowToken);
if (mParentSessionId != NO_SESSION_ID) {
pw.print(", parentId="); pw.print(mParentSessionId);
}
@@ -352,6 +372,7 @@
builder.append("act=").append(ComponentName.flattenToShortString(mComponentName))
.append(", activityId=").append(mActivityId)
.append(", displayId=").append(mDisplayId)
+ .append(", windowToken=").append(mWindowToken)
.append(", flags=").append(mFlags);
} else {
builder.append("id=").append(mId);
@@ -381,6 +402,7 @@
parcel.writeParcelable(mComponentName, flags);
if (fromServer()) {
parcel.writeInt(mDisplayId);
+ parcel.writeStrongBinder(mWindowToken);
parcel.writeInt(mFlags);
mActivityId.writeToParcel(parcel, flags);
}
@@ -411,11 +433,12 @@
return clientContext;
} else {
final int displayId = parcel.readInt();
+ final IBinder windowToken = parcel.readStrongBinder();
final int flags = parcel.readInt();
final ActivityId activityId = new ActivityId(parcel);
return new ContentCaptureContext(clientContext, activityId, componentName,
- displayId, flags);
+ displayId, windowToken, flags);
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index ce6d034..4b2d3a9 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Selection;
@@ -122,6 +123,12 @@
*/
public static final int TYPE_VIEW_INSETS_CHANGED = 9;
+ /**
+ * Called before {@link #TYPE_VIEW_TREE_APPEARING}, or after the size of the window containing
+ * the views changed.
+ */
+ public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10;
+
/** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_VIEW_APPEARED,
@@ -132,7 +139,8 @@
TYPE_CONTEXT_UPDATED,
TYPE_SESSION_PAUSED,
TYPE_SESSION_RESUMED,
- TYPE_VIEW_INSETS_CHANGED
+ TYPE_VIEW_INSETS_CHANGED,
+ TYPE_WINDOW_BOUNDS_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType{}
@@ -150,6 +158,7 @@
private int mParentSessionId = NO_SESSION_ID;
private @Nullable ContentCaptureContext mClientContext;
private @Nullable Insets mInsets;
+ private @Nullable Rect mBounds;
private int mComposingStart = MAX_INVALID_VALUE;
private int mComposingEnd = MAX_INVALID_VALUE;
@@ -346,6 +355,13 @@
return this;
}
+ /** @hide */
+ @NonNull
+ public ContentCaptureEvent setBounds(@NonNull Rect bounds) {
+ mBounds = bounds;
+ return this;
+ }
+
/**
* Gets the type of the event.
*
@@ -419,6 +435,16 @@
}
/**
+ * Gets the {@link Rect} bounds of the window associated with the event. Valid bounds will only
+ * be returned if the type of the event is {@link #TYPE_WINDOW_BOUNDS_CHANGED}, otherwise they
+ * will be null.
+ */
+ @Nullable
+ public Rect getBounds() {
+ return mBounds;
+ }
+
+ /**
* Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED}
* or {@link #TYPE_VIEW_DISAPPEARED}.
*
@@ -489,6 +515,9 @@
if (mInsets != null) {
pw.print(", insets="); pw.println(mInsets);
}
+ if (mBounds != null) {
+ pw.print(", bounds="); pw.println(mBounds);
+ }
if (mComposingStart > MAX_INVALID_VALUE) {
pw.print(", composing("); pw.print(mComposingStart);
pw.print(", "); pw.print(mComposingEnd); pw.print(")");
@@ -533,6 +562,9 @@
if (mInsets != null) {
string.append(", insets=").append(mInsets);
}
+ if (mBounds != null) {
+ string.append(", bounds=").append(mBounds);
+ }
if (mComposingStart > MAX_INVALID_VALUE) {
string.append(", composing=[")
.append(mComposingStart).append(",").append(mComposingEnd).append("]");
@@ -568,6 +600,9 @@
if (mType == TYPE_VIEW_INSETS_CHANGED) {
parcel.writeParcelable(mInsets, flags);
}
+ if (mType == TYPE_WINDOW_BOUNDS_CHANGED) {
+ parcel.writeParcelable(mBounds, flags);
+ }
if (mType == TYPE_VIEW_TEXT_CHANGED) {
parcel.writeInt(mComposingStart);
parcel.writeInt(mComposingEnd);
@@ -608,6 +643,9 @@
if (type == TYPE_VIEW_INSETS_CHANGED) {
event.setInsets(parcel.readParcelable(null));
}
+ if (type == TYPE_WINDOW_BOUNDS_CHANGED) {
+ event.setBounds(parcel.readParcelable(null));
+ }
if (type == TYPE_VIEW_TEXT_CHANGED) {
event.setComposingIndex(parcel.readInt(), parcel.readInt());
event.restoreComposingSpan();
@@ -649,6 +687,8 @@
return "CONTEXT_UPDATED";
case TYPE_VIEW_INSETS_CHANGED:
return "VIEW_INSETS_CHANGED";
+ case TYPE_WINDOW_BOUNDS_CHANGED:
+ return "TYPE_WINDOW_BOUNDS_CHANGED";
default:
return "UKNOWN_TYPE: " + type;
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 4cf5532..98ef4e7 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -26,6 +26,7 @@
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_WINDOW_BOUNDS_CHANGED;
import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
@@ -38,6 +39,7 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -776,6 +778,14 @@
.setClientContext(context), FORCE_FLUSH));
}
+ /** public because is also used by ViewRootImpl */
+ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
+ .setBounds(bounds)
+ ));
+ }
+
@Override
void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
super.dump(prefix, pw);
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index b321ac0..a09c823 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -124,7 +124,6 @@
Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
- sendCloseSystemWindows();
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
startCallActivity();
@@ -147,7 +146,6 @@
dispatcher.performedLongPress(event);
if (isUserSetupComplete()) {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
// Broadcast an intent that the Camera button was longpressed
Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -178,7 +176,6 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
getSearchManager().stopSearch();
mContext.startActivity(intent);
// Only clear this if we successfully start the
@@ -272,7 +269,6 @@
@UnsupportedAppUsage
void startCallActivity() {
- sendCloseSystemWindows();
Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -319,10 +315,6 @@
return mMediaSessionManager;
}
- void sendCloseSystemWindows() {
- PhoneWindow.sendCloseSystemWindows(mContext, null);
- }
-
private void handleVolumeKeyEvent(KeyEvent keyEvent) {
getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
AudioManager.USE_DEFAULT_STREAM_TYPE);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 50cd70f..5ce43df 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -870,6 +870,13 @@
transaction->setFixedTransformHint(ctrl, transformHint);
}
+static void nativeSetDropInputMode(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint mode) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setDropInputMode(ctrl, static_cast<gui::DropInputMode>(mode));
+}
+
static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
jlongArray array = env->NewLongArray(displayIds.size());
@@ -891,6 +898,12 @@
return array;
}
+static jlong nativeGetPrimaryPhysicalDisplayId(JNIEnv* env, jclass clazz) {
+ PhysicalDisplayId displayId;
+ SurfaceComposerClient::getPrimaryPhysicalDisplayId(&displayId);
+ return static_cast<jlong>(displayId.value);
+}
+
static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong physicalDisplayId) {
sp<IBinder> token =
SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId(physicalDisplayId));
@@ -1892,6 +1905,8 @@
(void*)nativeReleaseFrameRateFlexibilityToken },
{"nativeGetPhysicalDisplayIds", "()[J",
(void*)nativeGetPhysicalDisplayIds },
+ {"nativeGetPrimaryPhysicalDisplayId", "()J",
+ (void*)nativeGetPrimaryPhysicalDisplayId },
{"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;",
(void*)nativeGetPhysicalDisplayToken },
{"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
@@ -2009,6 +2024,8 @@
(void*)nativeGetTransformHint },
{"nativeSetTrustedOverlay", "(JJZ)V",
(void*)nativeSetTrustedOverlay },
+ {"nativeSetDropInputMode", "(JJI)V",
+ (void*)nativeSetDropInputMode },
// clang-format on
};
diff --git a/core/res/res/drawable-nodpi/default_wallpaper.png b/core/res/res/drawable-nodpi/default_wallpaper.png
index 490ebee..5152972 100644
--- a/core/res/res/drawable-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
new file mode 100644
index 0000000..26376fb
--- /dev/null
+++ b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
new file mode 100644
index 0000000..490ebee
--- /dev/null
+++ b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 8a3d734..3259201 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -40,6 +40,5 @@
android:maxLines="2"
android:paddingTop="12dp"
android:paddingBottom="12dp"
- android:lineHeight="20sp"
android:textAppearance="@style/TextAppearance.Toast"/>
</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a5f5051..0a6b02d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8371,6 +8371,17 @@
@hide @SystemApi -->
<attr name="supportsAmbientMode" format="boolean" />
+ <!-- Indicates that this wallpaper service should receive zoom updates when unfolding.
+ When this value is set to true
+ {@link android.service.wallpaper.WallpaperService.Engine} could receive zoom updates
+ when folding or unfolding a foldable device. Wallpapers receive zoom updates using
+ {@link android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)} and
+ zoom rendering should be handled manually. Default value is true.
+ When set to false wallpapers can implement custom folding/unfolding behavior
+ by listening to {@link android.hardware.Sensor#TYPE_HINGE_ANGLE}.
+ Corresponds to {@link android.app.WallpaperInfo#shouldUseDefaultUnfoldTransition()} -->
+ <attr name="shouldUseDefaultUnfoldTransition" format="boolean" />
+
<!-- Uri that specifies a settings Slice for this wallpaper. -->
<attr name="settingsSliceUri" format="string"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8b138f0..e3e5f84 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3219,6 +3219,11 @@
and one pSIM) -->
<integer name="config_num_physical_slots">1</integer>
+ <!-- When a radio power off request is received, we will delay completing the request until
+ either IMS moves to the deregistered state or the timeout defined by this configuration
+ elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
+ <integer name="config_delay_for_ims_dereg_millis">0</integer>
+
<!--Thresholds for LTE dbm in status bar-->
<integer-array translatable="false" name="config_lteDbmThresholds">
<item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
@@ -4596,6 +4601,13 @@
<!-- Indicates whether device has a power button fingerprint sensor. -->
<bool name="config_is_powerbutton_fps" translatable="false" >false</bool>
+ <!-- When each intermediate UDFPS enroll stage ends, as a fraction of total progress. -->
+ <string-array name="config_udfps_enroll_stage_thresholds" translatable="false">
+ <item>0.25</item>
+ <item>0.5</item>
+ <item>0.75</item>
+ </string-array>
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b57055c..7d48904 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3221,6 +3221,7 @@
<eat-comment />
<staging-public-group type="attr" first-id="0x01ff0000">
+ <public name="shouldUseDefaultUnfoldTransition" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01fe0000">
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index bac9cf2..9553f95 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -966,6 +966,7 @@
<style name="TextAppearance.Toast">
<item name="fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="textSize">14sp</item>
+ <item name="lineHeight">20sp</item>
<item name="textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7d2ae7e..b6c7659 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -486,6 +486,7 @@
<java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
<java-symbol type="string" name="config_deviceSpecificAudioService" />
<java-symbol type="integer" name="config_num_physical_slots" />
+ <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
<java-symbol type="array" name="config_integrityRuleProviderPackages" />
<java-symbol type="bool" name="config_useAssistantVolume" />
<java-symbol type="string" name="config_bandwidthEstimateSource" />
@@ -2612,6 +2613,7 @@
<java-symbol type="array" name="config_sfps_sensor_props" />
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
+ <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -4417,7 +4419,7 @@
<java-symbol type="string" name="view_and_control_notification_title" />
<java-symbol type="string" name="view_and_control_notification_content" />
<java-symbol type="array" name="config_accessibility_allowed_install_source" />
-
+
<!-- Translation -->
<java-symbol type="string" name="ui_translation_accessibility_translated_text" />
<java-symbol type="string" name="ui_translation_accessibility_translation_finished" />
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java
index ddb6729..4b19391 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java
@@ -39,9 +39,10 @@
public void testConstructorAdditionalFlags() {
final ComponentName componentName = new ComponentName("component", "name");
final IBinder token = new Binder();
+ final IBinder windowToken = new Binder();
final ContentCaptureContext ctx = new ContentCaptureContext(/* clientContext= */ null,
new ActivityId(/* taskId= */ 666, token), componentName, /* displayId= */
- 42, /* flags= */ 1);
+ 42, windowToken, /* flags= */ 1);
final ContentCaptureContext newCtx = new ContentCaptureContext(ctx, /* extraFlags= */ 2);
assertThat(newCtx.getFlags()).isEqualTo(3);
assertThat(newCtx.getActivityComponent()).isEqualTo(componentName);
@@ -50,6 +51,7 @@
assertThat(activityId.getTaskId()).isEqualTo(666);
assertThat(activityId.getToken()).isEqualTo(token);
assertThat(newCtx.getDisplayId()).isEqualTo(42);
+ assertThat(newCtx.getWindowToken()).isEqualTo(windowToken);
assertThat(newCtx.getExtras()).isNull();
assertThat(newCtx.getLocusId()).isNull();
assertThat(newCtx.getParentSessionId()).isNull();
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 119c9391..6adac13 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -103,12 +103,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
- "-2006946193": {
- "message": "setClientVisible: %s clientVisible=%b Callers=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-2002500255": {
"message": "Defer removing snapshot surface in %dms",
"level": "VERBOSE",
@@ -511,6 +505,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1556507536": {
+ "message": "Passing transform hint %d for window %s%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"-1554521902": {
"message": "showInsets(ime) was requested by different window: %s ",
"level": "WARN",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
deleted file mode 100644
index ce4e103..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
-import androidx.window.extensions.organizer.EmbeddingExtensionImpl;
-
-/**
- * Provider class that will instantiate the library implementation. It must be included in the
- * vendor library, and the vendor implementation must match the signature of this class.
- */
-public class ExtensionProvider {
- /**
- * Provides a simple implementation of {@link ExtensionInterface} that can be replaced by
- * an OEM by overriding this method.
- */
- public static ExtensionInterface getExtensionImpl(Context context) {
- return new SampleExtensionImpl(context);
- }
-
- /** Provides a reference implementation of {@link ActivityEmbeddingComponent}. */
- public static ActivityEmbeddingComponent getActivityEmbeddingExtensionImpl(
- @NonNull Context context) {
- return new EmbeddingExtensionImpl();
- }
-
- /**
- * The support library will use this method to check API version compatibility.
- * @return API version string in MAJOR.MINOR.PATCH-description format.
- */
- public static String getApiVersion() {
- return "1.0.0-settings_sample";
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
deleted file mode 100644
index 6a53efe..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions;
-
-import android.app.Activity;
-
-import androidx.annotation.NonNull;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Basic implementation of the {@link ExtensionInterface}. An OEM can choose to use it as the base
- * class for their implementation.
- */
-abstract class StubExtension implements ExtensionInterface {
-
- private ExtensionCallback mExtensionCallback;
- private final Set<Activity> mWindowLayoutChangeListenerActivities = new HashSet<>();
-
- StubExtension() {
- }
-
- @Override
- public void setExtensionCallback(@NonNull ExtensionCallback extensionCallback) {
- this.mExtensionCallback = extensionCallback;
- }
-
- @Override
- public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
- this.mWindowLayoutChangeListenerActivities.add(activity);
- this.onListenersChanged();
- }
-
- @Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
- this.mWindowLayoutChangeListenerActivities.remove(activity);
- this.onListenersChanged();
- }
-
- void updateWindowLayout(@NonNull Activity activity,
- @NonNull ExtensionWindowLayoutInfo newLayout) {
- if (this.mExtensionCallback != null) {
- mExtensionCallback.onWindowLayoutChanged(activity, newLayout);
- }
- }
-
- @NonNull
- Set<Activity> getActivitiesListeningForLayoutChanges() {
- return mWindowLayoutChangeListenerActivities;
- }
-
- protected boolean hasListeners() {
- return !mWindowLayoutChangeListenerActivities.isEmpty();
- }
-
- protected abstract void onListenersChanged();
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
new file mode 100644
index 0000000..990d7b6
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions;
+
+import android.app.ActivityThread;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
+import androidx.window.extensions.embedding.SplitController;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+
+/**
+ * The reference implementation of {@link WindowExtensions} that implements the initial API version.
+ */
+public class WindowExtensionsImpl implements WindowExtensions {
+
+ private final Object mLock = new Object();
+ private volatile WindowLayoutComponent mWindowLayoutComponent;
+ private volatile SplitController mSplitController;
+
+ @Override
+ public int getVendorApiLevel() {
+ return 1;
+ }
+
+ @Override
+ public boolean isWindowLayoutComponentAvailable() {
+ return true;
+ }
+
+ @Override
+ public WindowLayoutComponent getWindowLayoutComponent() {
+ if (mWindowLayoutComponent == null) {
+ synchronized (mLock) {
+ if (mWindowLayoutComponent == null) {
+ Context context = ActivityThread.currentApplication();
+ mWindowLayoutComponent = new WindowLayoutComponentImpl(context);
+ }
+ }
+ }
+ return mWindowLayoutComponent;
+ }
+
+ /**
+ * Returns {@code true} if {@link ActivityEmbeddingComponent} is present on the device,
+ * {@code false} otherwise. If the component is not available the developer will receive a
+ * single callback with empty data or default values where possible.
+ */
+ @Override
+ public boolean isEmbeddingComponentAvailable() {
+ return true;
+ }
+
+ /**
+ * Returns the OEM implementation of {@link ActivityEmbeddingComponent} if it is supported on
+ * the device. The implementation must match the API level reported in
+ * {@link androidx.window.extensions.WindowExtensions}. An
+ * {@link UnsupportedOperationException} will be thrown if the device does not support
+ * Activity Embedding. Use
+ * {@link WindowExtensions#isEmbeddingComponentAvailable()} to determine if
+ * {@link ActivityEmbeddingComponent} is present.
+ * @return the OEM implementation of {@link ActivityEmbeddingComponent}
+ */
+ @NonNull
+ public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
+ if (mSplitController == null) {
+ synchronized (mLock) {
+ if (mSplitController == null) {
+ mSplitController = new SplitController();
+ }
+ }
+ }
+ return mSplitController;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java
new file mode 100644
index 0000000..f9e1f07
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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 androidx.window.extensions;
+
+import android.annotation.NonNull;
+
+/**
+ * Provides the OEM implementation of {@link WindowExtensions}.
+ */
+public class WindowExtensionsProvider {
+
+ private static final WindowExtensions sWindowExtensions = new WindowExtensionsImpl();
+
+ /**
+ * Returns the OEM implementation of {@link WindowExtensions}. This method is implemented in
+ * the library provided on the device and overwrites one in the Jetpack library included in
+ * apps.
+ * @return the OEM implementation of {@link WindowExtensions}
+ */
+ @NonNull
+ public static WindowExtensions getWindowExtensions() {
+ return sWindowExtensions;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
similarity index 98%
rename from libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
rename to libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 46c8ffe..85ef270 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.extensions.organizer;
+package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -36,7 +36,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.window.extensions.embedding.SplitRule;
import java.util.Map;
import java.util.concurrent.Executor;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
similarity index 93%
rename from libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java
rename to libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index a41557d..06e7d14 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -14,15 +14,11 @@
* limitations under the License.
*/
-package androidx.window.extensions.organizer;
+package androidx.window.extensions.embedding;
import android.annotation.NonNull;
import android.app.Activity;
-import androidx.window.extensions.embedding.SplitPairRule;
-import androidx.window.extensions.embedding.SplitPlaceholderRule;
-import androidx.window.extensions.embedding.SplitRule;
-
/**
* Client-side descriptor of a split that holds two containers.
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
similarity index 88%
rename from libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
rename to libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index a783fcd..e1c8b11 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.extensions.organizer;
+package androidx.window.extensions.embedding;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,19 +31,10 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.util.Pair;
import android.window.TaskFragmentAppearedInfo;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
-import androidx.window.extensions.embedding.ActivityRule;
-import androidx.window.extensions.embedding.EmbeddingRule;
-import androidx.window.extensions.embedding.SplitInfo;
-import androidx.window.extensions.embedding.SplitPairRule;
-import androidx.window.extensions.embedding.SplitPlaceholderRule;
-import androidx.window.extensions.embedding.SplitRule;
-import androidx.window.extensions.embedding.TaskFragment;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -53,7 +44,8 @@
/**
* Main controller class that manages split states and presentation.
*/
-public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback {
+public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
+ ActivityEmbeddingComponent {
private final SplitPresenter mPresenter;
@@ -64,6 +56,7 @@
// Callback to Jetpack to notify about changes to split states.
private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
public SplitController() {
mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
@@ -77,6 +70,7 @@
}
/** Updates the embedding rules applied to future activity launches. */
+ @Override
public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
mSplitRules.clear();
mSplitRules.addAll(rules);
@@ -103,7 +97,8 @@
/**
* Registers the split organizer callback to notify about changes to active splits.
*/
- public void setEmbeddingCallback(@NonNull Consumer<List<SplitInfo>> callback) {
+ @Override
+ public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) {
mEmbeddingCallback = callback;
updateCallbackIfNecessary();
}
@@ -119,8 +114,8 @@
container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo());
if (container.isFinished()) {
mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
- updateCallbackIfNecessary();
}
+ updateCallbackIfNecessary();
}
@Override
@@ -139,8 +134,8 @@
final boolean shouldFinishDependent =
!taskFragmentInfo.isTaskClearedForReuse();
mPresenter.cleanupContainer(container, shouldFinishDependent);
- updateCallbackIfNecessary();
}
+ updateCallbackIfNecessary();
}
@Override
@@ -164,18 +159,23 @@
}
}
+ void onActivityCreated(@NonNull Activity launchedActivity) {
+ handleActivityCreated(launchedActivity);
+ updateCallbackIfNecessary();
+ }
+
/**
* Checks if the activity start should be routed to a particular container. It can create a new
* container for the activity and a new split container if necessary.
*/
// TODO(b/190433398): Break down into smaller functions.
- void onActivityCreated(@NonNull Activity launchedActivity) {
+ void handleActivityCreated(@NonNull Activity launchedActivity) {
final List<EmbeddingRule> splitRules = getSplitRules();
final TaskFragmentContainer currentContainer = getContainerWithActivity(
launchedActivity.getActivityToken(), launchedActivity);
// Check if the activity is configured to always be expanded.
- if (shouldExpand(launchedActivity, splitRules)) {
+ if (shouldExpand(launchedActivity, null, splitRules)) {
if (shouldContainerBeExpanded(currentContainer)) {
// Make sure that the existing container is expanded
mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
@@ -240,8 +240,6 @@
mPresenter.createNewSplitContainer(activityBelow, launchedActivity,
splitPairRule);
-
- updateCallbackIfNecessary();
}
private void onActivityConfigurationChanged(@NonNull Activity activity) {
@@ -501,7 +499,7 @@
continue;
}
SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) rule;
- if (placeholderRule.getActivityPredicate().test(activity)) {
+ if (placeholderRule.matchesActivity(activity)) {
return placeholderRule;
}
}
@@ -515,8 +513,16 @@
if (mEmbeddingCallback == null) {
return;
}
- // TODO(b/190433398): Check if something actually changed
- mEmbeddingCallback.accept(getActiveSplitStates());
+ if (!allActivitiesCreated()) {
+ return;
+ }
+ List<SplitInfo> currentSplitStates = getActiveSplitStates();
+ if (mLastReportedSplitStates.equals(currentSplitStates)) {
+ return;
+ }
+ mLastReportedSplitStates.clear();
+ mLastReportedSplitStates.addAll(currentSplitStates);
+ mEmbeddingCallback.accept(currentSplitStates);
}
/**
@@ -525,20 +531,46 @@
private List<SplitInfo> getActiveSplitStates() {
List<SplitInfo> splitStates = new ArrayList<>();
for (SplitContainer container : mSplitContainers) {
- TaskFragment primaryContainer =
- new TaskFragment(
+ if (container.getPrimaryContainer().isEmpty()
+ || container.getSecondaryContainer().isEmpty()) {
+ // Skipping containers that do not have any activities to report.
+ continue;
+ }
+ ActivityStack primaryContainer =
+ new ActivityStack(
container.getPrimaryContainer().collectActivities());
- TaskFragment secondaryContainer =
- new TaskFragment(
+ ActivityStack secondaryContainer =
+ new ActivityStack(
container.getSecondaryContainer().collectActivities());
SplitInfo splitState = new SplitInfo(primaryContainer,
- secondaryContainer, container.getSplitRule().getSplitRatio());
+ secondaryContainer,
+ // Splits that are not showing side-by-side are reported as having 0 split
+ // ratio, since by definition in the API the primary container occupies no
+ // width of the split when covered by the secondary.
+ mPresenter.shouldShowSideBySide(container)
+ ? container.getSplitRule().getSplitRatio()
+ : 0.0f);
splitStates.add(splitState);
}
return splitStates;
}
/**
+ * Checks if all activities that are registered with the containers have already appeared in
+ * the client.
+ */
+ private boolean allActivitiesCreated() {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.getInfo() == null
+ || container.getInfo().getActivities().size()
+ != container.collectActivities().size()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns {@code true} if the container is expanded to occupy full task size.
* Returns {@code false} if the container is included in an active split.
*/
@@ -567,8 +599,7 @@
continue;
}
SplitPairRule pairRule = (SplitPairRule) rule;
- if (pairRule.getActivityIntentPredicate().test(
- new Pair(primaryActivity, secondaryActivityIntent))) {
+ if (pairRule.matchesActivityIntentPair(primaryActivity, secondaryActivityIntent)) {
return pairRule;
}
}
@@ -587,10 +618,9 @@
}
SplitPairRule pairRule = (SplitPairRule) rule;
final Intent intent = secondaryActivity.getIntent();
- if (pairRule.getActivityPairPredicate().test(
- new Pair(primaryActivity, secondaryActivity))
- && (intent == null || pairRule.getActivityIntentPredicate().test(
- new Pair(primaryActivity, intent)))) {
+ if (pairRule.matchesActivityPair(primaryActivity, secondaryActivity)
+ && (intent == null
+ || pairRule.matchesActivityIntentPair(primaryActivity, intent))) {
return pairRule;
}
}
@@ -611,7 +641,7 @@
* Returns {@code true} if an Activity with the provided component name should always be
* expanded to occupy full task bounds. Such activity must not be put in a split.
*/
- private static boolean shouldExpand(@NonNull Activity activity,
+ private static boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent,
List<EmbeddingRule> splitRules) {
if (splitRules == null) {
return false;
@@ -624,7 +654,9 @@
if (!activityRule.shouldAlwaysExpand()) {
continue;
}
- if (activityRule.getActivityPredicate().test(activity)) {
+ if (activity != null && activityRule.matchesActivity(activity)) {
+ return true;
+ } else if (intent != null && activityRule.matchesIntent(intent)) {
return true;
}
}
@@ -678,11 +710,11 @@
/** Executor that posts on the main application thread. */
private static class MainThreadExecutor implements Executor {
- private final Handler handler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
- handler.post(r);
+ mHandler.post(r);
}
}
@@ -703,13 +735,25 @@
}
final Activity launchingActivity = (Activity) who;
- if (!setLaunchingToSideContainer(launchingActivity, intent, options)) {
+ if (shouldExpand(null, intent, getSplitRules())) {
+ setLaunchingInExpandedContainer(launchingActivity, options);
+ } else if (!setLaunchingToSideContainer(launchingActivity, intent, options)) {
setLaunchingInSameContainer(launchingActivity, intent, options);
}
return super.onStartActivity(who, intent, options);
}
+ private void setLaunchingInExpandedContainer(Activity launchingActivity, Bundle options) {
+ TaskFragmentContainer newContainer = mPresenter.createNewExpandedContainer(
+ launchingActivity);
+
+ // Amend the request to let the WM know that the activity should be placed in the
+ // dedicated container.
+ options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ newContainer.getTaskFragmentToken());
+ }
+
/**
* Returns {@code true} if the activity that is going to be started via the
* {@code intent} should be paired with the {@code launchingActivity} and is set to be
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
similarity index 80%
rename from libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
rename to libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index ac85ac8..25292b9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -14,16 +14,19 @@
* limitations under the License.
*/
-package androidx.window.extensions.organizer;
+package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.LayoutDirection;
+import android.view.View;
import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.TaskFragmentCreationParams;
@@ -32,8 +35,6 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.window.extensions.embedding.SplitPairRule;
-import androidx.window.extensions.embedding.SplitRule;
import java.util.concurrent.Executor;
@@ -42,13 +43,13 @@
* {@link SplitController}.
*/
class SplitPresenter extends JetpackTaskFragmentOrganizer {
- private static final int POSITION_LEFT = 0;
- private static final int POSITION_RIGHT = 1;
+ private static final int POSITION_START = 0;
+ private static final int POSITION_END = 1;
private static final int POSITION_FILL = 2;
@IntDef(value = {
- POSITION_LEFT,
- POSITION_RIGHT,
+ POSITION_START,
+ POSITION_END,
POSITION_FILL,
})
private @interface Position {}
@@ -96,13 +97,15 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
final Rect parentBounds = getParentContainerBounds(primaryActivity);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr(primaryActivity, rule));
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null);
// Create new empty task fragment
- TaskFragmentContainer secondaryContainer = mController.newContainer(null);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(null);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
+ rule, isLtr(primaryActivity, rule));
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
primaryActivity.getActivityToken(), secondaryRectBounds,
WINDOWING_MODE_MULTI_WINDOW);
@@ -135,11 +138,13 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
final Rect parentBounds = getParentContainerBounds(primaryActivity);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr(primaryActivity, rule));
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
+ isLtr(primaryActivity, rule));
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
secondaryActivity, secondaryRectBounds, primaryContainer);
@@ -153,6 +158,20 @@
}
/**
+ * Creates a new expanded container.
+ */
+ TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
+ final TaskFragmentContainer newContainer = mController.newContainer(null);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ createTaskFragment(wct, newContainer.getTaskFragmentToken(),
+ launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW);
+
+ applyTransaction(wct);
+ return newContainer;
+ }
+
+ /**
* Creates a new container or resizes an existing container for activity to the provided bounds.
* @param activity The activity to be re-parented to the container if necessary.
* @param containerToAvoid Re-parent from this container if an activity is already in it.
@@ -197,8 +216,10 @@
void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule) {
final Rect parentBounds = getParentContainerBounds(launchingActivity);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr(launchingActivity, rule));
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
+ isLtr(launchingActivity, rule));
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
launchingActivity.getActivityToken());
@@ -231,8 +252,15 @@
// Getting the parent bounds using the updated container - it will have the recent value.
final Rect parentBounds = getParentContainerBounds(updatedContainer);
final SplitRule rule = splitContainer.getSplitRule();
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
+ final Activity activity = splitContainer.getPrimaryContainer().getTopNonFinishingActivity();
+ if (activity == null) {
+ return;
+ }
+ final boolean isLtr = isLtr(activity, rule);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
+ isLtr);
// If the task fragments are not registered yet, the positions will be updated after they
// are created again.
@@ -283,36 +311,64 @@
// TODO(b/190433398): Supply correct insets.
final WindowMetrics parentMetrics = new WindowMetrics(parentBounds,
new WindowInsets(new Rect()));
- return rule.getParentWindowMetricsPredicate().test(parentMetrics);
+ return rule.checkParentMetrics(parentMetrics);
}
@NonNull
private Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds,
- @NonNull SplitRule rule) {
+ @NonNull SplitRule rule, boolean isLtr) {
if (!shouldShowSideBySide(parentBounds, rule)) {
return new Rect();
}
- float splitRatio = rule.getSplitRatio();
+ final float splitRatio = rule.getSplitRatio();
+ final float rtlSplitRatio = 1 - splitRatio;
switch (position) {
- case POSITION_LEFT:
- return new Rect(
- parentBounds.left,
- parentBounds.top,
- (int) (parentBounds.left + parentBounds.width() * splitRatio),
- parentBounds.bottom);
- case POSITION_RIGHT:
- return new Rect(
- (int) (parentBounds.left + parentBounds.width() * splitRatio),
- parentBounds.top,
- parentBounds.right,
- parentBounds.bottom);
+ case POSITION_START:
+ return isLtr ? getLeftContainerBounds(parentBounds, splitRatio)
+ : getRightContainerBounds(parentBounds, rtlSplitRatio);
+ case POSITION_END:
+ return isLtr ? getRightContainerBounds(parentBounds, splitRatio)
+ : getLeftContainerBounds(parentBounds, rtlSplitRatio);
case POSITION_FILL:
return parentBounds;
}
return parentBounds;
}
+ private Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
+ return new Rect(
+ parentBounds.left,
+ parentBounds.top,
+ (int) (parentBounds.left + parentBounds.width() * splitRatio),
+ parentBounds.bottom);
+ }
+
+ private Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
+ return new Rect(
+ (int) (parentBounds.left + parentBounds.width() * splitRatio),
+ parentBounds.top,
+ parentBounds.right,
+ parentBounds.bottom);
+ }
+
+ /**
+ * Checks if a split with the provided rule should be displays in left-to-right layout
+ * direction, either always or with the current configuration.
+ */
+ private boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
+ switch (rule.getLayoutDirection()) {
+ case LayoutDirection.LOCALE:
+ return context.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
+ case LayoutDirection.RTL:
+ return false;
+ case LayoutDirection.LTR:
+ default:
+ return true;
+ }
+ }
+
@NonNull
Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
final Configuration parentConfig = mFragmentParentConfigs.get(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
new file mode 100644
index 0000000..65bd9f3
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -0,0 +1,99 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
+ */
+class TaskFragmentAnimationAdapter {
+ private final Animation mAnimation;
+ private final RemoteAnimationTarget mTarget;
+ private final SurfaceControl mLeash;
+ private final boolean mSizeChanged;
+ private final Transformation mTransformation = new Transformation();
+ private final float[] mMatrix = new float[9];
+ private final float[] mVecs = new float[4];
+ private final Rect mRect = new Rect();
+ private boolean mIsFirstFrame = true;
+
+ TaskFragmentAnimationAdapter(@NonNull Animation animation,
+ @NonNull RemoteAnimationTarget target) {
+ this(animation, target, target.leash, false /* sizeChanged */);
+ }
+
+ /**
+ * @param sizeChanged whether the surface size needs to be changed.
+ */
+ TaskFragmentAnimationAdapter(@NonNull Animation animation,
+ @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
+ boolean sizeChanged) {
+ mAnimation = animation;
+ mTarget = target;
+ mLeash = leash;
+ mSizeChanged = sizeChanged;
+ }
+
+ /** Called on frame update. */
+ void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+ if (mIsFirstFrame) {
+ t.show(mLeash);
+ mIsFirstFrame = false;
+ }
+
+ currentPlayTime = Math.min(currentPlayTime, mAnimation.getDuration());
+ mAnimation.getTransformation(currentPlayTime, mTransformation);
+ mTransformation.getMatrix().postTranslate(
+ mTarget.localBounds.left, mTarget.localBounds.top);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+
+ if (mSizeChanged) {
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ mVecs[1] = mVecs[2] = 0;
+ mVecs[0] = mVecs[3] = 1;
+ mTransformation.getMatrix().mapVectors(mVecs);
+ mVecs[0] = 1.f / mVecs[0];
+ mVecs[3] = 1.f / mVecs[3];
+ final Rect clipRect = mTransformation.getClipRect();
+ mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
+ mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
+ mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
+ mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
+ t.setWindowCrop(mLeash, mRect);
+ }
+ }
+
+ /** Called after animation finished. */
+ void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+ onAnimationUpdate(t, mAnimation.getDuration());
+ }
+
+ long getDurationHint() {
+ return mAnimation.computeDurationHint();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
similarity index 93%
rename from libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationController.java
rename to libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index b85287d..6579766 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.extensions.organizer;
+package androidx.window.extensions.embedding;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
@@ -45,7 +45,7 @@
}
final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
final RemoteAnimationAdapter animationAdapter =
- new RemoteAnimationAdapter(mRemoteRunner, 0, 0);
+ new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
new file mode 100644
index 0000000..bb37fff
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -0,0 +1,216 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** To run the TaskFragment animations. */
+class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
+
+ private static final String TAG = "TaskFragAnimationRunner";
+ private final Handler mHandler = new Handler(Looper.myLooper());
+ private final TaskFragmentAnimationSpec mAnimationSpec;
+
+ TaskFragmentAnimationRunner() {
+ mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
+ }
+
+ @Nullable
+ private Animator mAnimator;
+
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] apps,
+ @NonNull RemoteAnimationTarget[] wallpapers,
+ @NonNull RemoteAnimationTarget[] nonApps,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ if (wallpapers.length != 0 || nonApps.length != 0) {
+ throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
+ + "wallpaper or non-app windows.");
+ }
+ if (TaskFragmentAnimationController.DEBUG) {
+ Log.v(TAG, "onAnimationStart transit=" + transit);
+ }
+ mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ if (TaskFragmentAnimationController.DEBUG) {
+ Log.v(TAG, "onAnimationCancelled");
+ }
+ mHandler.post(this::cancelAnimation);
+ }
+
+ /** Creates and starts animation. */
+ private void startAnimation(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ if (mAnimator != null) {
+ Log.w(TAG, "start new animation when the previous one is not finished yet.");
+ mAnimator.cancel();
+ }
+ mAnimator = createAnimator(transit, targets, finishedCallback);
+ mAnimator.start();
+ }
+
+ /** Cancels animation. */
+ private void cancelAnimation() {
+ if (mAnimator == null) {
+ return;
+ }
+ mAnimator.cancel();
+ mAnimator = null;
+ }
+
+ /** Creates the animator given the transition type and windows. */
+ private Animator createAnimator(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ final List<TaskFragmentAnimationAdapter> adapters =
+ createAnimationAdapters(transit, targets);
+ long duration = 0;
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ duration = Math.max(duration, adapter.getDurationHint());
+ }
+ final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.setDuration(duration);
+ animator.addUpdateListener((anim) -> {
+ // Update all adapters in the same transaction.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ }
+ t.apply();
+ });
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ adapter.onAnimationEnd(t);
+ }
+ t.apply();
+
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+ return animator;
+ }
+
+ /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+ private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
+ @WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets) {
+ switch (transit) {
+ case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
+ return createOpenAnimationAdapters(targets);
+ case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
+ return createCloseAnimationAdapters(targets);
+ case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
+ return createChangeAnimationAdapters(targets);
+ default:
+ throw new IllegalArgumentException("Unhandled transit type=" + transit);
+ }
+ }
+
+ private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : targets) {
+ final Animation animation =
+ mAnimationSpec.loadOpenAnimation(target.mode != MODE_CLOSING /* isEnter */);
+ adapters.add(new TaskFragmentAnimationAdapter(animation, target));
+ }
+ return adapters;
+ }
+
+ private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : targets) {
+ final Animation animation =
+ mAnimationSpec.loadCloseAnimation(target.mode != MODE_CLOSING /* isEnter */);
+ adapters.add(new TaskFragmentAnimationAdapter(animation, target));
+ }
+ return adapters;
+ }
+
+ private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : targets) {
+ if (target.startBounds != null) {
+ final Animation[] animations =
+ mAnimationSpec.createChangeBoundsChangeAnimations(target);
+ adapters.add(new TaskFragmentAnimationAdapter(animations[0], target,
+ target.startLeash, false /* sizeChanged */));
+ adapters.add(new TaskFragmentAnimationAdapter(animations[1], target,
+ target.leash, true /* sizeChanged */));
+ continue;
+ }
+
+ final Animation animation;
+ if (target.hasAnimatingParent) {
+ // No-op if it will be covered by the changing parent window.
+ animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
+ } else if (target.mode == MODE_CLOSING) {
+ animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
+ } else {
+ animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
+ }
+ adapters.add(new TaskFragmentAnimationAdapter(animation, target));
+ }
+ return adapters;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
new file mode 100644
index 0000000..7129590
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -0,0 +1,199 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.RemoteAnimationTarget;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.policy.AttributeCache;
+import com.android.internal.policy.TransitionAnimation;
+
+/** Animation spec for TaskFragment transition. */
+class TaskFragmentAnimationSpec {
+
+ private static final String TAG = "TaskFragAnimationSpec";
+ private static final int CHANGE_ANIMATION_DURATION = 517;
+ private static final int CHANGE_ANIMATION_FADE_DURATION = 82;
+ private static final int CHANGE_ANIMATION_FADE_OFFSET = 67;
+
+ private final Context mContext;
+ private final TransitionAnimation mTransitionAnimation;
+ private final Interpolator mFastOutExtraSlowInInterpolator;
+ private float mTransitionAnimationScaleSetting;
+
+ TaskFragmentAnimationSpec(@NonNull Handler handler) {
+ mContext = ActivityThread.currentActivityThread().getApplication();
+ mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
+ // Initialize the AttributeCache for the TransitionAnimation.
+ AttributeCache.init(mContext);
+ mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_extra_slow_in);
+
+ // The transition animation should be adjusted based on the developer option.
+ final ContentResolver resolver = mContext.getContentResolver();
+ mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
+ Settings.Global.TRANSITION_ANIMATION_SCALE,
+ mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
+ new SettingsObserver(handler));
+ }
+
+ /** For target that doesn't need to be animated. */
+ static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
+ // Noop but just keep the target showing/hiding.
+ final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
+ return new AlphaAnimation(alpha, alpha);
+ }
+
+ /** Animation for target that is opening in a change transition. */
+ Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
+ final Rect bounds = target.localBounds;
+ // The target will be animated in from left or right depends on its position.
+ final int startLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /** Animation for target that is closing in a change transition. */
+ Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
+ final Rect bounds = target.localBounds;
+ // The target will be animated out to left or right depends on its position.
+ final int endLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /**
+ * Animation for target that is changing (bounds change) in a change transition.
+ * @return the return array always has two elements. The first one is for the start leash, and
+ * the second one is for the end leash.
+ */
+ Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
+ final Rect startBounds = target.startBounds;
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect endBounds = target.localBounds;
+ float scaleX = ((float) startBounds.width()) / endBounds.width();
+ float scaleY = ((float) startBounds.height()) / endBounds.height();
+ // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
+ // be scaled up with its parent.
+ float startScaleX = 1.f / scaleX;
+ float startScaleY = 1.f / scaleY;
+
+ // The start leash will be fade out.
+ final AnimationSet startSet = new AnimationSet(true /* shareInterpolator */);
+ startSet.setInterpolator(mFastOutExtraSlowInInterpolator);
+ final Animation startAlpha = new AlphaAnimation(1f, 0f);
+ startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
+ startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
+ startSet.addAnimation(startAlpha);
+ final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
+ startScaleY);
+ startScale.setDuration(CHANGE_ANIMATION_DURATION);
+ startSet.addAnimation(startScale);
+ startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
+ endBounds.height());
+ startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ // The end leash will be moved into the end position while scaling.
+ final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
+ endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
+ final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
+ endScale.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endScale);
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
+ 0, 0);
+ endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endTranslate);
+ // The end leash is resizing, we should update the window crop based on the clip rect.
+ final Rect startClip = new Rect(startBounds);
+ final Rect endClip = new Rect(endBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(clipAnim);
+ endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
+ parentBounds.height());
+ endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ return new Animation[]{startSet, endSet};
+ }
+
+ Animation loadOpenAnimation(boolean isEnter) {
+ // TODO(b/196173550) We need to customize the animation to handle two open window as one.
+ return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
+ ? R.styleable.WindowAnimation_activityOpenEnterAnimation
+ : R.styleable.WindowAnimation_activityOpenExitAnimation);
+ }
+
+ Animation loadCloseAnimation(boolean isEnter) {
+ // TODO(b/196173550) We need to customize the animation to handle two open window as one.
+ return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
+ ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : R.styleable.WindowAnimation_activityCloseExitAnimation);
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ SettingsObserver(@NonNull Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mTransitionAnimationScaleSetting = Settings.Global.getFloat(
+ mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
+ mTransitionAnimationScaleSetting);
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
similarity index 98%
rename from libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
rename to libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 8503b9f..54e44a7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.extensions.organizer;
+package androidx.window.extensions.embedding;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -212,7 +212,9 @@
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
// Finish own activities
for (Activity activity : collectActivities()) {
- activity.finish();
+ if (!activity.isFinishing()) {
+ activity.finish();
+ }
}
if (!shouldFinishDependent) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
similarity index 63%
rename from libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
rename to libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a0d5b00..383d91d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.extensions;
+package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -36,19 +36,27 @@
import androidx.window.util.PriorityDataProducer;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
/**
- * Reference implementation of androidx.window.extensions OEM interface for use with
+ * Reference implementation of androidx.window.extensions.layout OEM interface for use with
* WindowManager Jetpack.
*
* NOTE: This version is a work in progress and under active development. It MUST NOT be used in
* production builds since the interface can still change before reaching stable version.
* Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
*/
-class SampleExtensionImpl extends StubExtension {
+public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = "SampleExtension";
+ private static WindowLayoutComponent sInstance;
+
+ private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
+ new HashMap<>();
private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
private final DataProducer<Integer> mDevicePostureProducer;
@@ -56,7 +64,7 @@
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
- SampleExtensionImpl(Context context) {
+ public WindowLayoutComponentImpl(Context context) {
mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
mDevicePostureProducer = new PriorityDataProducer<>(List.of(
mSettingsDevicePostureProducer,
@@ -73,28 +81,68 @@
mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
+ /**
+ * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
+ * @param activity hosting a {@link android.view.Window}
+ * @param consumer interested in receiving updates to {@link WindowLayoutInfo}
+ */
+ public void addWindowLayoutInfoListener(@NonNull Activity activity,
+ @NonNull Consumer<WindowLayoutInfo> consumer) {
+ mWindowLayoutChangeListeners.put(activity, consumer);
+ updateRegistrations();
+ }
+
+ /**
+ * Removes a listener no longer interested in receiving updates.
+ * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
+ */
+ public void removeWindowLayoutInfoListener(
+ @NonNull Consumer<WindowLayoutInfo> consumer) {
+ mWindowLayoutChangeListeners.values().remove(consumer);
+ updateRegistrations();
+ }
+
+ void updateWindowLayout(@NonNull Activity activity,
+ @NonNull WindowLayoutInfo newLayout) {
+ Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity);
+ if (consumer != null) {
+ consumer.accept(newLayout);
+ }
+ }
+
+ @NonNull
+ Set<Activity> getActivitiesListeningForLayoutChanges() {
+ return mWindowLayoutChangeListeners.keySet();
+ }
+
+ protected boolean hasListeners() {
+ return !mWindowLayoutChangeListeners.isEmpty();
+ }
+
private int getFeatureState(DisplayFeature feature) {
Integer featureState = feature.getState();
Optional<Integer> posture = mDevicePostureProducer.getData();
- int fallbackPosture = posture.orElse(ExtensionFoldingFeature.STATE_FLAT);
+ int fallbackPosture = posture.orElse(FoldingFeature.STATE_FLAT);
return featureState == null ? fallbackPosture : featureState;
}
private void onDisplayFeaturesChanged() {
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
- ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
+ WindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
updateWindowLayout(activity, newLayout);
}
}
@NonNull
- private ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
- List<ExtensionDisplayFeature> displayFeatures = getDisplayFeatures(activity);
- return new ExtensionWindowLayoutInfo(displayFeatures);
+ private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
+ List<androidx.window.extensions.layout.DisplayFeature> displayFeatures =
+ getDisplayFeatures(activity);
+ return new WindowLayoutInfo(displayFeatures);
}
- private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
- List<ExtensionDisplayFeature> features = new ArrayList<>();
+ private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures(
+ @NonNull Activity activity) {
+ List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>();
int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays");
@@ -115,15 +163,14 @@
rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(activity, featureRect);
- features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
+ features.add(new FoldingFeature(featureRect, baseFeature.getType(),
getFeatureState(baseFeature)));
}
}
return features;
}
- @Override
- protected void onListenersChanged() {
+ private void updateRegistrations() {
if (hasListeners()) {
mSettingsDevicePostureProducer.registerObserversIfNeeded();
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/EmbeddingExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/EmbeddingExtensionImpl.java
deleted file mode 100644
index 9a8961f..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/EmbeddingExtensionImpl.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.organizer;
-
-import androidx.annotation.NonNull;
-import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
-import androidx.window.extensions.embedding.EmbeddingRule;
-import androidx.window.extensions.embedding.SplitInfo;
-
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * Reference implementation of the activity embedding interface defined in WM Jetpack.
- */
-public class EmbeddingExtensionImpl implements ActivityEmbeddingComponent {
-
- private final SplitController mSplitController;
-
- public EmbeddingExtensionImpl() {
- mSplitController = new SplitController();
- }
-
- @Override
- public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
- mSplitController.setEmbeddingRules(rules);
- }
-
- @Override
- public void setEmbeddingCallback(@NonNull Consumer<List<SplitInfo>> consumer) {
- mSplitController.setEmbeddingCallback(consumer);
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java
deleted file mode 100644
index 9ee60d8..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.organizer;
-
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-
-import androidx.annotation.Nullable;
-
-/** To run the TaskFragment animations. */
-class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
-
- private static final String TAG = "TaskFragAnimationRunner";
- private final Handler mHandler = new Handler(Looper.myLooper());
-
- @Nullable
- private IRemoteAnimationFinishedCallback mFinishedCallback;
-
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- if (wallpapers.length != 0 || nonApps.length != 0) {
- throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
- + "wallpaper or non-app windows.");
- }
- if (TaskFragmentAnimationController.DEBUG) {
- Log.v(TAG, "onAnimationStart transit=" + transit);
- }
- mHandler.post(() -> startAnimation(apps, finishedCallback));
- }
-
- @Override
- public void onAnimationCancelled() {
- if (TaskFragmentAnimationController.DEBUG) {
- Log.v(TAG, "onAnimationCancelled");
- }
- mHandler.post(this::onAnimationFinished);
- }
-
- private void startAnimation(RemoteAnimationTarget[] targets,
- IRemoteAnimationFinishedCallback finishedCallback) {
- // TODO(b/196173550) replace with actual animations
- mFinishedCallback = finishedCallback;
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (RemoteAnimationTarget target : targets) {
- if (target.mode == MODE_OPENING) {
- t.show(target.leash);
- t.setAlpha(target.leash, 1);
- }
- t.setPosition(target.leash, target.localBounds.left, target.localBounds.top);
- }
- t.apply();
- onAnimationFinished();
- }
-
- private void onAnimationFinished() {
- if (mFinishedCallback == null) {
- return;
- }
- try {
- mFinishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- mFinishedCallback = null;
- }
-}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 097febf..42e829e 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_bottom.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_bottom.xml
deleted file mode 100644
index 18dc909..0000000
--- a/libs/WindowManager/Shell/res/drawable/split_rounded_bottom.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/split_divider_corner_size"
- android:height="@dimen/split_divider_corner_size"
- android:viewportWidth="42"
- android:viewportHeight="42">
-
- <group android:pivotX="21"
- android:pivotY="21"
- android:rotation="180">
- <path
- android:fillColor="@color/split_divider_background"
- android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_left.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_left.xml
deleted file mode 100644
index 931cacf..0000000
--- a/libs/WindowManager/Shell/res/drawable/split_rounded_left.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/split_divider_corner_size"
- android:height="@dimen/split_divider_corner_size"
- android:viewportWidth="42"
- android:viewportHeight="42">
-
- <group android:pivotX="21"
- android:pivotY="21"
- android:rotation="-90">
- <path
- android:fillColor="@color/split_divider_background"
- android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_right.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_right.xml
deleted file mode 100644
index 54e4761..0000000
--- a/libs/WindowManager/Shell/res/drawable/split_rounded_right.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/split_divider_corner_size"
- android:height="@dimen/split_divider_corner_size"
- android:viewportWidth="42"
- android:viewportHeight="42">
-
- <group android:pivotX="21"
- android:pivotY="21"
- android:rotation="90">
- <path
- android:fillColor="@color/split_divider_background"
- android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_top.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_top.xml
deleted file mode 100644
index 9115b5a..0000000
--- a/libs/WindowManager/Shell/res/drawable/split_rounded_top.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/split_divider_corner_size"
- android:height="@dimen/split_divider_corner_size"
- android:viewportWidth="42"
- android:viewportHeight="42">
-
- <path
- android:fillColor="@color/split_divider_background"
- android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" />
-
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index 94182cd..e3be700 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -24,20 +24,20 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <View style="@style/DockedDividerTopLeftRoundCorner"/>
-
<View
style="@style/DockedDividerBackground"
android:id="@+id/docked_divider_background"/>
- <View style="@style/DockedDividerBottomRightRoundCorner"/>
-
<com.android.wm.shell.common.split.DividerHandleView
style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
android:contentDescription="@string/accessibility_divider"
android:background="@null"/>
+ <com.android.wm.shell.common.split.DividerRoundedCorner
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
</FrameLayout>
</com.android.wm.shell.common.split.DividerView>
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
index e5707f3..9eddac4 100644
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ b/libs/WindowManager/Shell/res/values-land/styles.xml
@@ -28,20 +28,6 @@
<item name="android:layout_height">96dp</item>
</style>
- <style name="DockedDividerTopLeftRoundCorner">
- <item name="android:layout_gravity">center_horizontal|top</item>
- <item name="android:background">@drawable/split_rounded_top</item>
- <item name="android:layout_width">@dimen/split_divider_corner_size</item>
- <item name="android:layout_height">@dimen/split_divider_corner_size</item>
- </style>
-
- <style name="DockedDividerBottomRightRoundCorner">
- <item name="android:layout_gravity">center_horizontal|bottom</item>
- <item name="android:background">@drawable/split_rounded_bottom</item>
- <item name="android:layout_width">@dimen/split_divider_corner_size</item>
- <item name="android:layout_height">@dimen/split_divider_corner_size</item>
- </style>
-
<style name="DockedDividerMinimizedShadow">
<item name="android:layout_width">8dp</item>
<item name="android:layout_height">match_parent</item>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 28ff25a..cb6d4de 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -37,20 +37,6 @@
<item name="android:background">@color/split_divider_background</item>
</style>
- <style name="DockedDividerTopLeftRoundCorner">
- <item name="android:layout_gravity">center_vertical|left</item>
- <item name="android:background">@drawable/split_rounded_left</item>
- <item name="android:layout_width">@dimen/split_divider_corner_size</item>
- <item name="android:layout_height">@dimen/split_divider_corner_size</item>
- </style>
-
- <style name="DockedDividerBottomRightRoundCorner">
- <item name="android:layout_gravity">center_vertical|right</item>
- <item name="android:background">@drawable/split_rounded_right</item>
- <item name="android:layout_width">@dimen/split_divider_corner_size</item>
- <item name="android:layout_height">@dimen/split_divider_corner_size</item>
- </style>
-
<style name="DockedDividerMinimizedShadow">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">8dp</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 9d0dd3c..d590ab1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -968,7 +968,7 @@
}
});
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
- if (mExpandedBubble != null) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setTaskViewAlpha(
(float) valueAnimator.getAnimatedValue());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
new file mode 100644
index 0000000..364bb65
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -0,0 +1,160 @@
+/*
+ * 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 static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.RoundedCorner;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/**
+ * Draws inverted rounded corners beside divider bar to keep splitting tasks cropped with proper
+ * rounded corners.
+ */
+public class DividerRoundedCorner extends View {
+ private final int mDividerWidth;
+ private final Paint mDividerBarBackground;
+ private final Point mStartPos = new Point();
+ private InvertedRoundedCornerDrawInfo mTopLeftCorner;
+ private InvertedRoundedCornerDrawInfo mTopRightCorner;
+ private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
+ private InvertedRoundedCornerDrawInfo mBottomRightCorner;
+
+ public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mDividerWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mDividerBarBackground = new Paint();
+ mDividerBarBackground.setColor(
+ getResources().getColor(R.color.split_divider_background, null));
+ mDividerBarBackground.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mDividerBarBackground.setStyle(Paint.Style.FILL);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTopLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_LEFT);
+ mTopRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_RIGHT);
+ mBottomLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_LEFT);
+ mBottomRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_RIGHT);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.save();
+
+ mTopLeftCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mTopLeftCorner.mPath, mDividerBarBackground);
+
+ canvas.translate(-mStartPos.x, -mStartPos.y);
+ mTopRightCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mTopRightCorner.mPath, mDividerBarBackground);
+
+ canvas.translate(-mStartPos.x, -mStartPos.y);
+ mBottomLeftCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mBottomLeftCorner.mPath, mDividerBarBackground);
+
+ canvas.translate(-mStartPos.x, -mStartPos.y);
+ mBottomRightCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mBottomRightCorner.mPath, mDividerBarBackground);
+
+ canvas.restore();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ private boolean isLandscape() {
+ return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ }
+
+ /**
+ * Holds draw information of the inverted rounded corner at a specific position.
+ *
+ * @see {@link com.android.launcher3.taskbar.TaskbarDragLayer}
+ */
+ private class InvertedRoundedCornerDrawInfo {
+ @RoundedCorner.Position
+ private final int mCornerPosition;
+
+ private final int mRadius;
+
+ private final Path mPath = new Path();
+
+ InvertedRoundedCornerDrawInfo(@RoundedCorner.Position int cornerPosition) {
+ mCornerPosition = cornerPosition;
+
+ final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(cornerPosition);
+ mRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
+
+ // Starts with a filled square, and then subtracting out a circle from the appropriate
+ // corner.
+ final Path square = new Path();
+ square.addRect(0, 0, mRadius, mRadius, Path.Direction.CW);
+ final Path circle = new Path();
+ circle.addCircle(
+ isLeftCorner() ? mRadius : 0 /* x */,
+ isTopCorner() ? mRadius : 0 /* y */,
+ mRadius, Path.Direction.CW);
+ mPath.op(square, circle, Path.Op.DIFFERENCE);
+ }
+
+ private void calculateStartPos(Point outPos) {
+ if (isLandscape()) {
+ // Place left corner at the right side of the divider bar.
+ outPos.x = isLeftCorner()
+ ? getWidth() / 2 + mDividerWidth / 2
+ : getWidth() / 2 - mDividerWidth / 2 - mRadius;
+ outPos.y = isTopCorner() ? 0 : getHeight() - mRadius;
+ } else {
+ outPos.x = isLeftCorner() ? 0 : getWidth() - mRadius;
+ // Place top corner at the bottom of the divider bar.
+ outPos.y = isTopCorner()
+ ? getHeight() / 2 + mDividerWidth / 2
+ : getHeight() / 2 - mDividerWidth / 2 - mRadius;
+ }
+ }
+
+ private boolean isLeftCorner() {
+ return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_BOTTOM_LEFT;
+ }
+
+ private boolean isTopCorner() {
+ return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_TOP_RIGHT;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index f35c8e1..f9ba97f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -24,7 +24,6 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Property;
-import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -178,10 +177,12 @@
return true;
}
+ // Convert to use screen-based coordinates to prevent lost track of motion events while
+ // moving divider bar and calculating dragging velocity.
+ event.setLocation(event.getRawX(), event.getRawY());
final int action = event.getAction() & MotionEvent.ACTION_MASK;
final boolean isLandscape = isLandscape();
- // Using raw xy to prevent lost track of motion events while moving divider bar.
- final int touchPos = isLandscape ? (int) event.getRawX() : (int) event.getRawY();
+ final int touchPos = (int) (isLandscape ? event.getX() : event.getY());
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index e6d6028..bde2b5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -71,23 +71,13 @@
+ " topIsHome:" + topIsHome);
}
- final int visibleSplashScreenType = legacySplashScreen
- ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-
if (!topIsHome) {
- if (!processRunning) {
+ if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
return useEmptySplashScreen
? STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
- : visibleSplashScreenType;
- }
- if (newTask) {
- return useEmptySplashScreen
- ? STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
- : visibleSplashScreenType;
- }
- if (taskSwitch && !activityCreated) {
- return visibleSplashScreenType;
+ : legacySplashScreen
+ ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
}
}
if (taskSwitch && allowTaskSnapshot) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index ac612a7..c798ace 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -71,7 +71,7 @@
void removeFiltered(RemoteTransition remote) {
boolean removed = false;
for (int i = mFilters.size() - 1; i >= 0; --i) {
- if (mFilters.get(i).second == remote) {
+ if (mFilters.get(i).second.asBinder().equals(remote.asBinder())) {
mFilters.remove(i);
removed = true;
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index bc10401..6d52b66 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -436,4 +436,8 @@
oneway void setSpatializerGlobalTransform(in float[] transform);
oneway void recenterHeadTracker();
+
+ void setSpatializerParameter(int key, in byte[] value);
+
+ void getSpatializerParameter(int key, inout byte[] value);
}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index b062eea..8b1624b 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -129,6 +129,14 @@
*/
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
+ /**
+ * @hide
+ * Constant indicating the {@code Spatializer} on this device supports the spatialization of
+ * multichannel bed plus objects.
+ * @see #getImmersiveAudioLevel()
+ */
+ public static final int SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS = 2;
+
/** @hide */
@IntDef(flag = false, value = {
HEAD_TRACKING_MODE_UNSUPPORTED,
@@ -792,6 +800,45 @@
}
}
+ /**
+ * @hide
+ * Sets a parameter on the platform spatializer effect implementation.
+ * This is to be used for vendor-specific configurations of their effect, keys and values are
+ * not reuseable across implementations.
+ * @param key the parameter to change
+ * @param value an array for the value of the parameter to change
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setEffectParameter(int key, @NonNull byte[] value) {
+ Objects.requireNonNull(value);
+ try {
+ mAm.getService().setSpatializerParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setEffectParameter", e);
+ }
+ }
+
+ /**
+ * @hide
+ * Retrieves a parameter value from the platform spatializer effect implementation.
+ * This is to be used for vendor-specific configurations of their effect, keys and values are
+ * not reuseable across implementations.
+ * @param key the parameter for which the value is queried
+ * @param value a non-empty array to contain the return value. The caller is responsible for
+ * passing an array of size matching the parameter.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void getEffectParameter(int key, @NonNull byte[] value) {
+ Objects.requireNonNull(value);
+ try {
+ mAm.getService().getSpatializerParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getEffectParameter", e);
+ }
+ }
+
//-----------------------------------------------------------------------------
// callback helper definitions
diff --git a/packages/SettingsLib/res/values-as/arrays.xml b/packages/SettingsLib/res/values-as/arrays.xml
index 50bfbe0..89739f6 100644
--- a/packages/SettingsLib/res/values-as/arrays.xml
+++ b/packages/SettingsLib/res/values-as/arrays.xml
@@ -228,7 +228,7 @@
<item msgid="8612549335720461635">"৪কে. (সুৰক্ষিত)"</item>
<item msgid="7322156123728520872">"৪কে. (বৰ্ধিত)"</item>
<item msgid="7735692090314849188">"৪কে. (বৰ্ধিত, সুৰক্ষিত)"</item>
- <item msgid="7346816300608639624">"৭২০পি., ১০৮০পি. (দ্বৈত স্ক্ৰীণ)"</item>
+ <item msgid="7346816300608639624">"৭২০পি., ১০৮০পি. (দ্বৈত স্ক্ৰীন)"</item>
</string-array>
<string-array name="enable_opengl_traces_entries">
<item msgid="4433736508877934305">"নাই"</item>
@@ -243,7 +243,7 @@
</string-array>
<string-array name="track_frame_time_entries">
<item msgid="634406443901014984">"অফ হৈ আছে"</item>
- <item msgid="1288760936356000927">"স্ক্ৰীণত দণ্ড হিচাপে"</item>
+ <item msgid="1288760936356000927">"স্ক্ৰীনত দণ্ড হিচাপে"</item>
<item msgid="5023908510820531131">"<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>ত"</item>
</string-array>
<string-array name="debug_hw_overdraw_entries">
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 1ae3452..9f9b11d 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -238,7 +238,7 @@
<string name="bugreport_in_power" msgid="8664089072534638709">"বাগ ৰিপৰ্টৰ শ্ৱৰ্টকাট"</string>
<string name="bugreport_in_power_summary" msgid="1885529649381831775">"পাৱাৰ মেনুত বাগ প্ৰতিবেদন গ্ৰহণ কৰিবলৈ এটা বুটাম দেখুৱাওক"</string>
<string name="keep_screen_on" msgid="1187161672348797558">"জাগ্ৰত কৰি ৰাখক"</string>
- <string name="keep_screen_on_summary" msgid="1510731514101925829">"চ্চাৰ্জ হৈ থকাৰ সময়ত স্ক্ৰীণ কেতিয়াও সুপ্ত অৱস্থালৈ নাযায়"</string>
+ <string name="keep_screen_on_summary" msgid="1510731514101925829">"চ্চাৰ্জ হৈ থকাৰ সময়ত স্ক্ৰীন কেতিয়াও সুপ্ত অৱস্থালৈ নাযায়"</string>
<string name="bt_hci_snoop_log" msgid="7291287955649081448">"ব্লুটুথ HCI স্নুপ ল’গ সক্ষম কৰক"</string>
<string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পিছত ব্লুটুথ ট’গল কৰক)"</string>
<string name="oem_unlock_enable" msgid="5334869171871566731">"ঔইএম আনলক"</string>
@@ -331,9 +331,9 @@
<string name="media_category" msgid="8122076702526144053">"মিডিয়া"</string>
<string name="debug_monitoring_category" msgid="1597387133765424994">"নিৰীক্ষণ কৰি থকা হৈছে"</string>
<string name="strict_mode" msgid="889864762140862437">"কঠোৰ ম’ড সক্ষম কৰা হৈছে"</string>
- <string name="strict_mode_summary" msgid="1838248687233554654">"যেতিয়া এপসমূহে মুখ্য থ্ৰেডত দীঘলীয়া কাৰ্যকলাপ চলাই, তেতিয়া স্ক্ৰীণ ফ্লাশ্ব কৰক"</string>
+ <string name="strict_mode_summary" msgid="1838248687233554654">"যেতিয়া এপ্সমূহে মুখ্য থ্ৰেডত দীঘলীয়া কাৰ্যকলাপ চলাই, তেতিয়া স্ক্ৰীন ফ্লাশ্ব কৰক"</string>
<string name="pointer_location" msgid="7516929526199520173">"পইণ্টাৰৰ অৱস্থান"</string>
- <string name="pointer_location_summary" msgid="957120116989798464">"চলিত স্পৰ্শ-বিষয়ক তথ্যসহ স্ক্ৰীণ অভাৰলে\'"</string>
+ <string name="pointer_location_summary" msgid="957120116989798464">"চলিত স্পৰ্শ-বিষয়ক তথ্যসহ স্ক্ৰীন অভাৰলে’"</string>
<string name="show_touches" msgid="8437666942161289025">"টেপসমূহ দেখুৱাওক"</string>
<string name="show_touches_summary" msgid="3692861665994502193">"টিপিলে দৃশ্যায়িত ফীডবেক দিয়ক"</string>
<string name="show_screen_updates" msgid="2078782895825535494">"পৃষ্ঠভাগৰ আপডেইট দেখুৱাওক"</string>
@@ -344,7 +344,7 @@
<string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"হাৰ্ডৱেৰ লেয়াৰ আপডেইট হওতে সিঁহতক সেউজীয়া ৰঙেৰে ফ্লাশ্ব কৰক"</string>
<string name="debug_hw_overdraw" msgid="8944851091008756796">"GPU অভাৰড্ৰ ডিবাগ কৰক"</string>
<string name="disable_overlays" msgid="4206590799671557143">"HW অ’ভাৰলে অক্ষম কৰক"</string>
- <string name="disable_overlays_summary" msgid="1954852414363338166">"স্ক্ৰীণ কম্প’জিট কৰাৰ বাবে সদায় জিপিইউ ব্যৱহাৰ কৰক"</string>
+ <string name="disable_overlays_summary" msgid="1954852414363338166">"স্ক্ৰীন কম্প’জিট কৰাৰ বাবে সদায় জিপিইউ ব্যৱহাৰ কৰক"</string>
<string name="simulate_color_space" msgid="1206503300335835151">"ৰঙৰ ঠাই ছিমিউলেইট কৰক"</string>
<string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL ট্ৰেছ সক্ষম কৰক"</string>
<string name="usb_audio_disable_routing" msgid="3367656923544254975">"ইউএছবি অডিঅ\' ৰাউটিং অক্ষম কৰক"</string>
@@ -352,7 +352,7 @@
<string name="debug_layout" msgid="1659216803043339741">"লেআউটৰ সময় দেখুৱাওক"</string>
<string name="debug_layout_summary" msgid="8825829038287321978">"ক্লিপ বাউণ্ড, মাৰ্জিন আদিসমূহ দেখুৱাওক"</string>
<string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"আৰটিএল চানেকিৰ দিশ বলেৰে সলনি কৰক"</string>
- <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"সকলো ভাষাৰ বাবে স্ক্ৰীণৰ চানেকিৰ দিশ RTLলৈ বলেৰে সলনি কৰক"</string>
+ <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"সকলো ভাষাৰ বাবে স্ক্ৰীনৰ চানেকিৰ দিশ RTLলৈ বলেৰে সলনি কৰক"</string>
<string name="window_blurs" msgid="6831008984828425106">"ৱিণ্ড’ স্তৰত অস্পষ্ট কৰাৰ অনুমতি দিয়ক"</string>
<string name="force_msaa" msgid="4081288296137775550">"বল ৪গুণ MSAA"</string>
<string name="force_msaa_summary" msgid="9070437493586769500">"OpenGL ES 2.0 এপত ৪গুণ MSAA সক্ষম কৰক"</string>
@@ -373,7 +373,7 @@
<string name="show_all_anrs" msgid="9160563836616468726">"নেপথ্য এএনআৰবোৰ দেখুৱাওক"</string>
<string name="show_all_anrs_summary" msgid="8562788834431971392">"নেপথ্য এপসমূহৰ বাবে এপে সঁহাৰি দিয়া নাই ডায়ল\'গ প্ৰদৰ্শন কৰক"</string>
<string name="show_notification_channel_warnings" msgid="3448282400127597331">"জাননী চ্চেনেলৰ সকীয়নিসমূহ দেখুৱাওক"</string>
- <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"কোনো এপে বৈধ চ্চেনেল নোহোৱাকৈ কোনো জাননী প\'ষ্ট কৰিলে স্ক্ৰীণত সকীয়নি প্ৰদৰ্শন হয়"</string>
+ <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"কোনো এপে বৈধ চ্চেনেল নোহোৱাকৈ কোনো জাননী প\'ষ্ট কৰিলে স্ক্ৰীনত সকীয়নি প্ৰদৰ্শন হয়"</string>
<string name="force_allow_on_external" msgid="9187902444231637880">"বাহ্যিক সঞ্চয়াগাৰত এপক বলেৰে অনুমতি দিয়ক"</string>
<string name="force_allow_on_external_summary" msgid="8525425782530728238">"মেনিফেষ্টৰ মান যিয়েই নহওক, বাহ্যিক সঞ্চয়াগাৰত লিখিবলৈ যিকোনো এপক উপযুক্ত কৰি তোলে"</string>
<string name="force_resizable_activities" msgid="7143612144399959606">"বলেৰে কাৰ্যকলাপসমূহৰ আকাৰ সলনি কৰিব পৰা কৰক"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 921caba..108d86d 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -286,7 +286,7 @@
<string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Réduit la décharge de la batterie et améliore les performances du réseau"</string>
<string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"Quand ce mode est activé, l\'adresse MAC de cet appareil peut changer chaque fois qu\'il se connecte à un réseau Wi-Fi où le changement aléatoire d\'adresse MAC est activé"</string>
<string name="wifi_metered_label" msgid="8737187690304098638">"Facturé à l\'usage"</string>
- <string name="wifi_unmetered_label" msgid="6174142840934095093">"Non facturé à l\'usage"</string>
+ <string name="wifi_unmetered_label" msgid="6174142840934095093">"Sans compteur"</string>
<string name="select_logd_size_title" msgid="1604578195914595173">"Tailles des tampons de l\'enregistreur"</string>
<string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Tailles enreg. par tampon journal"</string>
<string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Effacer l\'espace de stockage persistant de l\'enregistreur ?"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index cbf92af..0f381c9 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -542,7 +542,7 @@
<string name="delete_blob_text" msgid="2819192607255625697">"Ортақ деректерді жою"</string>
<string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Осы ортақ деректерді шынымен жойғыңыз келе ме?"</string>
<string name="user_add_user_item_summary" msgid="5748424612724703400">"Пайдаланушылардың өздерінің қолданбалары мен мазмұны болады"</string>
- <string name="user_add_profile_item_summary" msgid="5418602404308968028">"Өз есептік жазбаңыздан қолданбалар мен мазмұнға қол жетімділікті шектеуіңізге болады"</string>
+ <string name="user_add_profile_item_summary" msgid="5418602404308968028">"Өз аккаунтыңыздан қолданбалар мен мазмұнға қол жетімділікті шектеуіңізге болады"</string>
<string name="user_add_user_item_title" msgid="2394272381086965029">"Пайдаланушы"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Шектелген профайл"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Жаңа пайдаланушы қосылсын ба?"</string>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index 801c037..195fc47 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -28,7 +28,7 @@
<item msgid="2837871868181677206">"IP 주소를 가져오는 중..."</item>
<item msgid="4613015005934755724">"연결됨"</item>
<item msgid="3763530049995655072">"일시 정지됨"</item>
- <item msgid="7852381437933824454">"연결을 끊는 중…"</item>
+ <item msgid="7852381437933824454">"연결 해제 중…"</item>
<item msgid="5046795712175415059">"연결 끊김"</item>
<item msgid="2473654476624070462">"실패"</item>
<item msgid="9146847076036105115">"차단됨"</item>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 97ad0a0..c455c14 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -65,7 +65,7 @@
<string name="wifi_passpoint_expired" msgid="6540867261754427561">"만료됨"</string>
<string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"연결 끊김"</string>
- <string name="bluetooth_disconnecting" msgid="7638892134401574338">"연결을 끊는 중…"</string>
+ <string name="bluetooth_disconnecting" msgid="7638892134401574338">"연결 해제 중…"</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"연결 중…"</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"연결됨<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_pairing" msgid="4269046942588193600">"페어링 중..."</string>
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index b8039e1..e026012 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -37,8 +37,7 @@
const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
-const val MIN_CHROMA = 15
-const val MIN_LSTAR = 10
+const val MIN_CHROMA = 5
public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) {
@@ -75,7 +74,14 @@
get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
init {
- val seedArgb = if (seed == Color.TRANSPARENT) GOOGLE_BLUE else seed
+ val proposedSeedCam = Cam.fromInt(seed)
+ val seedArgb = if (seed == Color.TRANSPARENT) {
+ GOOGLE_BLUE
+ } else if (proposedSeedCam.chroma < 5) {
+ GOOGLE_BLUE
+ } else {
+ seed
+ }
val camSeed = Cam.fromInt(seedArgb)
val hue = camSeed.hue
val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA)
@@ -129,9 +135,7 @@
val distinctColors = wallpaperColors.mainColors.map {
it.toArgb()
}.distinct().filter {
- val cam = Cam.fromInt(it)
- val lstar = lstarFromInt(it)
- cam.chroma >= MIN_CHROMA && lstar >= MIN_LSTAR
+ Cam.fromInt(it).chroma >= MIN_CHROMA
}.toList()
if (distinctColors.isEmpty()) {
@@ -164,30 +168,38 @@
val cam = it.value
val lstar = lstarFromInt(it.key)
val proportion = intToHueProportion[it.key]!!
- cam.chroma >= MIN_CHROMA && lstar >= MIN_LSTAR &&
+ cam.chroma >= MIN_CHROMA &&
(totalPopulationMeaningless || proportion > 0.01)
}
// Sort the colors by score, from high to low.
- val seeds = mutableListOf<Int>()
val intToScoreIntermediate = filteredIntToCam.mapValues {
score(it.value, intToHueProportion[it.key]!!)
}
val intToScore = intToScoreIntermediate.entries.toMutableList()
intToScore.sortByDescending { it.value }
- // Go through the colors, from high score to low score. If there isn't already a seed
- // color with a hue close to color being examined, add the color being examined to the
- // seed colors.
- for (entry in intToScore) {
- val int = entry.key
- val existingSeedNearby = seeds.find {
- val hueA = intToCam[int]!!.hue
- val hueB = intToCam[it]!!.hue
- hueDiff(hueA, hueB) < 15 } != null
- if (existingSeedNearby) {
- continue
+ // Go through the colors, from high score to low score.
+ // If the color is distinct in hue from colors picked so far, pick the color.
+ // Iteratively decrease the amount of hue distinctness required, thus ensuring we
+ // maximize difference between colors.
+ val minimumHueDistance = 15
+ val seeds = mutableListOf<Int>()
+ maximizeHueDistance@ for (i in 90 downTo minimumHueDistance step 1) {
+ seeds.clear()
+ for (entry in intToScore) {
+ val int = entry.key
+ val existingSeedNearby = seeds.find {
+ val hueA = intToCam[int]!!.hue
+ val hueB = intToCam[it]!!.hue
+ hueDiff(hueA, hueB) < i } != null
+ if (existingSeedNearby) {
+ continue
+ }
+ seeds.add(int)
+ if (seeds.size >= 4) {
+ break@maximizeHueDistance
+ }
}
- seeds.add(int)
}
if (seeds.isEmpty()) {
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
index 498b7dd..aab3538 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
@@ -53,10 +53,15 @@
*/
public static @ColorInt int[] of(float hue, float chroma) {
int[] shades = new int[12];
- shades[0] = ColorUtils.CAMToColor(hue, chroma, 99);
- shades[1] = ColorUtils.CAMToColor(hue, chroma, 95);
+ // At tone 90 and above, blue and yellow hues can reach a much higher chroma.
+ // To preserve a consistent appearance across all hues, use a maximum chroma of 40.
+ shades[0] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 99);
+ shades[1] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 95);
for (int i = 2; i < 12; i++) {
float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1);
+ if (lStar >= 90) {
+ chroma = Math.min(40f, chroma);
+ }
shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar);
}
return shades;
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index a2ae5023..e854b02 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -104,4 +104,11 @@
allow it to use the whole screen space, 0.6 will allow it to use just under half of the
screen. -->
<item name="half_opened_bouncer_height_ratio" type="dimen" format="float">0.0</item>
+
+ <!-- The actual amount of translation that is applied to the bouncer when it animates from one
+ side of the screen to the other in one-handed mode. Note that it will always translate from
+ the side of the screen to the other (it will "jump" closer to the destination while the
+ opacity is zero), but this controls how much motion will actually be applied to it while
+ animating. Larger values will cause it to move "faster" while fading out/in. -->
+ <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index a21a63c..9cf09ff 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -15,19 +15,25 @@
~ limitations under the License
-->
<!-- This is a view that shows a user switcher in Keyguard. -->
-<com.android.systemui.statusbar.phone.UserAvatarView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyguard_qs_user_switch_view"
- android:layout_width="@dimen/kg_framed_avatar_size"
- android:layout_height="@dimen/kg_framed_avatar_size"
- android:layout_centerHorizontal="true"
- android:layout_gravity="top|end"
- android:layout_marginEnd="16dp"
- systemui:avatarPadding="0dp"
- systemui:badgeDiameter="18dp"
- systemui:badgeMargin="1dp"
- systemui:frameColor="@color/kg_user_avatar_frame"
- systemui:framePadding="0dp"
- systemui:frameWidth="0dp">
-</com.android.systemui.statusbar.phone.UserAvatarView>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end">
+ <com.android.systemui.statusbar.phone.UserAvatarView
+ android:id="@+id/kg_multi_user_avatar"
+ android:layout_width="@dimen/kg_framed_avatar_size"
+ android:layout_height="@dimen/kg_framed_avatar_size"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="16dp"
+ systemui:avatarPadding="0dp"
+ systemui:badgeDiameter="18dp"
+ systemui:badgeMargin="1dp"
+ systemui:frameColor="@color/kg_user_avatar_frame"
+ systemui:framePadding="0dp"
+ systemui:frameWidth="0dp">
+ </com.android.systemui.statusbar.phone.UserAvatarView>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index fc0d60a..542a1c9 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -39,7 +39,6 @@
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="48dp"
android:minHeight="@dimen/qs_header_row_min_height"
android:gravity="center_vertical|start"
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
@@ -51,6 +50,7 @@
android:id="@+id/date_clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/status_bar_left_clock_end_padding"
android:gravity="center_vertical|start"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.Status"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 4355751..4b6e58f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -70,6 +70,19 @@
android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"/>
+
+ <!-- Fingerprint -->
+ <!-- AOD dashed fingerprint icon with moving dashes -->
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
index 49b182a..a3fe8ef 100644
--- a/packages/SystemUI/res/layout/text_toast.xml
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -45,6 +45,5 @@
android:maxLines="2"
android:paddingTop="12dp"
android:paddingBottom="12dp"
- android:lineHeight="20sp"
android:textAppearance="@*android:style/TextAppearance.Toast"/>
</LinearLayout>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 19ec8ce..f057603 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -16,7 +16,7 @@
<resources>
<!-- Minimum margin between clock and top of screen or ambient indication -->
- <dimen name="keyguard_clock_top_margin">76dp</dimen>
+ <dimen name="keyguard_clock_top_margin">38dp</dimen>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c432395..32b85fe 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -87,7 +87,7 @@
<dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
<!-- End padding for left-aligned status bar clock -->
- <dimen name="status_bar_left_clock_end_padding">7dp</dimen>
+ <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
<!-- Spacing after the wifi signals that is present if there are any icons following it. -->
<dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
@@ -750,9 +750,7 @@
<!-- The margin between the status view and the notifications on Keyguard.-->
<dimen name="keyguard_status_view_bottom_margin">20dp</dimen>
<!-- Minimum margin between clock and status bar -->
- <dimen name="keyguard_clock_top_margin">36dp</dimen>
- <!-- The margin between top of clock and bottom of lock icon. -->
- <dimen name="keyguard_clock_lock_margin">16dp</dimen>
+ <dimen name="keyguard_clock_top_margin">18dp</dimen>
<!-- The amount to shift the clocks during a small/large transition -->
<dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
@@ -1157,9 +1155,9 @@
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
<!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
- direction that elements aer moved to prevent burn-in on AOD-->
- <dimen name="udfps_burn_in_offset_x">2dp</dimen>
- <dimen name="udfps_burn_in_offset_y">8dp</dimen>
+ direction that elements are moved to prevent burn-in on AOD-->
+ <dimen name="udfps_burn_in_offset_x">7px</dimen>
+ <dimen name="udfps_burn_in_offset_y">28px</dimen>
<dimen name="corner_size">8dp</dimen>
<dimen name="top_padding">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 92f89d6..efcf40a 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -160,9 +160,7 @@
mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mBatteryController.removeCallback(mBatteryCallback);
- if (!mView.isAttachedToWindow()) {
- mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
- }
+ mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
}
/** Animate the clock appearance */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 11c4b6a..260b393 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -72,13 +72,16 @@
* Clock for both small and large sizes
*/
private AnimatableClockController mClockViewController;
- private FrameLayout mClockFrame;
+ private FrameLayout mClockFrame; // top aligned clock
private AnimatableClockController mLargeClockViewController;
- private FrameLayout mLargeClockFrame;
+ private FrameLayout mLargeClockFrame; // centered clock
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardBypassController mBypassController;
+ private int mLargeClockTopMargin = 0;
+ private int mKeyguardClockTopMargin = 0;
+
/**
* Listener for changes to the color palette.
*
@@ -177,6 +180,8 @@
}
mColorExtractor.addOnColorsChangedListener(mColorsListener);
mView.updateColors(getGradientColors());
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
View ksa = mView.findViewById(R.id.keyguard_status_area);
@@ -241,6 +246,8 @@
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
updateClockLayout();
}
@@ -249,9 +256,12 @@
if (mSmartspaceController.isEnabled()) {
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
- lp.topMargin = getContext().getResources().getDimensionPixelSize(
+ mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize(
R.dimen.keyguard_large_clock_top_margin);
+ lp.topMargin = mLargeClockTopMargin;
mLargeClockFrame.setLayoutParams(lp);
+ } else {
+ mLargeClockTopMargin = 0;
}
}
@@ -363,6 +373,28 @@
}
}
+ /**
+ * Get y-bottom position of the currently visible clock on the keyguard.
+ * We can't directly getBottom() because clock changes positions in AOD for burn-in
+ */
+ int getClockBottom(int statusBarHeaderHeight) {
+ if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ View clock = mLargeClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view_large);
+ int frameHeight = mLargeClockFrame.getHeight();
+ int clockHeight = clock.getHeight();
+ return frameHeight / 2 + clockHeight / 2;
+ } else {
+ return mClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view).getHeight()
+ + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ }
+ }
+
+ boolean isClockTopAligned() {
+ return mLargeClockFrame.getVisibility() != View.VISIBLE;
+ }
+
private void updateAodIcons() {
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index a35aedf..1862fc7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -102,7 +102,7 @@
ConstraintSet cs = new ConstraintSet();
cs.clone(mContainer);
- cs.setGuidelinePercent(R.id.pin_pad_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+ cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
? halfOpenPercentage : 0.0f);
cs.applyTo(mContainer);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 64d214d..d80a408 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,8 +21,7 @@
import static java.lang.Integer.max;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@@ -40,6 +39,8 @@
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
@@ -55,7 +56,6 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import java.util.ArrayList;
import java.util.List;
@@ -85,6 +85,13 @@
private static final long IME_DISAPPEAR_DURATION_MS = 125;
+ // The duration of the animation to switch bouncer sides.
+ private static final long BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS = 500;
+
+ // How much of the switch sides animation should be dedicated to fading the bouncer out. The
+ // remainder will fade it back in again.
+ private static final float BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
+
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
private AlertDialog mAlertDialog;
@@ -322,18 +329,87 @@
? 0 : (int) (getMeasuredWidth() - mSecurityViewFlipper.getWidth());
if (animate) {
- mRunningOneHandedAnimator =
- mSecurityViewFlipper.animate().translationX(targetTranslation);
- mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRunningOneHandedAnimator = null;
+ // This animation is a bit fun to implement. The bouncer needs to move, and fade in/out
+ // at the same time. The issue is, the bouncer should only move a short amount (120dp or
+ // so), but obviously needs to go from one side of the screen to the other. This needs a
+ // pretty custom animation.
+ //
+ // This works as follows. It uses a ValueAnimation to simply drive the animation
+ // progress. This animator is responsible for both the translation of the bouncer, and
+ // the current fade. It will fade the bouncer out while also moving it along the 120dp
+ // path. Once the bouncer is fully faded out though, it will "snap" the bouncer closer
+ // to its destination, then fade it back in again. The effect is that the bouncer will
+ // move from 0 -> X while fading out, then (destination - X) -> destination while fading
+ // back in again.
+ // TODO(b/195012405): Make this animation properly abortable.
+ Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_extra_slow_in);
+ Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
+ Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
+
+ ValueAnimator anim = ValueAnimator.ofFloat(0.0f, 1.0f);
+ anim.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
+ anim.setInterpolator(Interpolators.LINEAR);
+
+ int initialTranslation = (int) mSecurityViewFlipper.getTranslationX();
+ int totalTranslation = (int) getResources().getDimension(
+ R.dimen.one_handed_bouncer_move_animation_translation);
+
+ final boolean shouldRestoreLayerType = mSecurityViewFlipper.hasOverlappingRendering()
+ && mSecurityViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
+ if (shouldRestoreLayerType) {
+ mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
+ }
+
+ anim.addUpdateListener(animation -> {
+ float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
+ boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
+
+ int currentTranslation = (int) (positionInterpolator.getInterpolation(
+ animation.getAnimatedFraction()) * totalTranslation);
+ int translationRemaining = totalTranslation - currentTranslation;
+
+ // Flip the sign if we're going from right to left.
+ if (mIsSecurityViewLeftAligned) {
+ currentTranslation = -currentTranslation;
+ translationRemaining = -translationRemaining;
+ }
+
+ if (isFadingOut) {
+ // The bouncer fades out over the first X%.
+ float fadeOutFraction = MathUtils.constrainedMap(
+ /* rangeMin= */0.0f,
+ /* rangeMax= */1.0f,
+ /* valueMin= */0.0f,
+ /* valueMax= */switchPoint,
+ animation.getAnimatedFraction());
+ float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
+ mSecurityViewFlipper.setAlpha(1f - opacity);
+
+ // Animate away from the source.
+ mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation);
+ } else {
+ // And in again over the remaining (100-X)%.
+ float fadeInFraction = MathUtils.constrainedMap(
+ /* rangeMin= */0.0f,
+ /* rangeMax= */1.0f,
+ /* valueMin= */switchPoint,
+ /* valueMax= */1.0f,
+ animation.getAnimatedFraction());
+
+ float opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
+ mSecurityViewFlipper.setAlpha(opacity);
+
+ // Fading back in, animate towards the destination.
+ mSecurityViewFlipper.setTranslationX(targetTranslation - translationRemaining);
+ }
+
+ if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
+ mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
}
});
- mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mRunningOneHandedAnimator.start();
+ anim.start();
} else {
mSecurityViewFlipper.setTranslationX(targetTranslation);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8bf8e09..d23b1c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -187,6 +187,20 @@
}
/**
+ * Get y-bottom position of the currently visible clock.
+ */
+ public int getClockBottom(int statusBarHeaderHeight) {
+ return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight);
+ }
+
+ /**
+ * @return true if the currently displayed clock is top aligned (as opposed to center aligned)
+ */
+ public boolean isClockTopAligned() {
+ return mKeyguardClockSwitchController.isClockTopAligned();
+ }
+
+ /**
* Set whether the view accessibility importance mode.
*/
public void setStatusAccessibilityImportance(int mode) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index dd3bb89..371564a 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -45,7 +45,7 @@
private int mRadius;
private ImageView mLockIcon;
- private ImageView mUnlockBgView;
+ private ImageView mBgView;
private int mLockIconColor;
@@ -58,19 +58,19 @@
public void onFinishInflate() {
super.onFinishInflate();
mLockIcon = findViewById(R.id.lock_icon);
- mUnlockBgView = findViewById(R.id.lock_icon_bg);
+ mBgView = findViewById(R.id.lock_icon_bg);
}
void updateColorAndBackgroundVisibility(boolean useBackground) {
- if (useBackground) {
+ if (useBackground && mLockIcon.getDrawable() != null) {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
- mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
- mUnlockBgView.setVisibility(View.VISIBLE);
+ mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
+ mBgView.setVisibility(View.VISIBLE);
} else {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
R.attr.wallpaperTextColorAccent);
- mUnlockBgView.setVisibility(View.GONE);
+ mBgView.setVisibility(View.GONE);
}
mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
@@ -78,6 +78,11 @@
void setImageDrawable(Drawable drawable) {
mLockIcon.setImageDrawable(drawable);
+ if (drawable == null) {
+ mBgView.setVisibility(View.INVISIBLE);
+ } else {
+ mBgView.setVisibility(View.VISIBLE);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index a115195..28e19ac 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -19,6 +19,8 @@
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
import android.content.Context;
import android.content.res.Configuration;
@@ -33,6 +35,7 @@
import android.os.Process;
import android.os.Vibrator;
import android.util.DisplayMetrics;
+import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
@@ -60,6 +63,8 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.airbnb.lottie.LottieAnimationView;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
@@ -95,6 +100,8 @@
@NonNull private final DelayableExecutor mExecutor;
private boolean mUdfpsEnrolled;
+ @NonNull private LottieAnimationView mAodFp;
+
@NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
@NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
@NonNull private final Drawable mLockIcon;
@@ -113,6 +120,7 @@
private boolean mIsKeyguardShowing;
private boolean mUserUnlockedWithBiometric;
private Runnable mCancelDelayedUpdateVisibilityRunnable;
+ private Runnable mOnGestureDetectedRunnable;
private boolean mUdfpsSupported;
private float mHeightPixels;
@@ -122,6 +130,12 @@
private boolean mShowUnlockIcon;
private boolean mShowLockIcon;
+ // for udfps when strong auth is required or unlocked on AOD
+ private boolean mShowAODFpIcon;
+ private final int mMaxBurnInOffsetX;
+ private final int mMaxBurnInOffsetY;
+ private float mInterpolatedDarkAmount;
+
private boolean mDownDetected;
private boolean mDetectedLongPress;
private final Rect mSensorTouchLocation = new Rect();
@@ -156,6 +170,12 @@
mAuthRippleController = authRippleController;
final Context context = view.getContext();
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ mMaxBurnInOffsetX = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+
mUnlockIcon = mView.getContext().getResources().getDrawable(
R.drawable.ic_unlock,
mView.getContext().getTheme());
@@ -174,19 +194,19 @@
@Override
protected void onInit() {
- mAuthController.addCallback(mAuthControllerCallback);
- mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
-
mView.setAccessibilityDelegate(mAccessibilityDelegate);
}
@Override
protected void onViewAttached() {
+ updateIsUdfpsEnrolled();
updateConfiguration();
updateKeyguardShowing();
mUserUnlockedWithBiometric = false;
+
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
mIsDozing = mStatusBarStateController.isDozing();
+ mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount();
mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
mStatusBarState = mStatusBarStateController.getState();
@@ -194,15 +214,18 @@
updateColors();
mConfigurationController.addCallback(mConfigurationListener);
+ mAuthController.addCallback(mAuthControllerCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
mDownDetected = false;
+ updateBurnInOffsets();
updateVisibility();
}
@Override
protected void onViewDetached() {
+ mAuthController.removeCallback(mAuthControllerCallback);
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -232,7 +255,7 @@
mCancelDelayedUpdateVisibilityRunnable = null;
}
- if (!mIsKeyguardShowing) {
+ if (!mIsKeyguardShowing && !mIsDozing) {
mView.setVisibility(View.INVISIBLE);
return;
}
@@ -243,6 +266,7 @@
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
&& (!mUdfpsEnrolled || !mRunningFPS);
mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
+ mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
@@ -265,10 +289,22 @@
}
mView.setVisibility(View.VISIBLE);
mView.setContentDescription(mUnlockedLabel);
+ } else if (mShowAODFpIcon) {
+ mView.setImageDrawable(null);
+ mView.setContentDescription(null);
+ mAodFp.setVisibility(View.VISIBLE);
+ mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel);
+ mView.setVisibility(View.VISIBLE);
} else {
mView.setVisibility(View.INVISIBLE);
mView.setContentDescription(null);
}
+
+ if (!mShowAODFpIcon) {
+ mAodFp.setVisibility(View.INVISIBLE);
+ mAodFp.setContentDescription(null);
+ }
+
if (!Objects.equals(prevContentDescription, mView.getContentDescription())
&& mView.getContentDescription() != null) {
mView.announceForAccessibility(mView.getContentDescription());
@@ -346,10 +382,12 @@
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mUdfpsSupported: " + mUdfpsSupported);
pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
+ pw.println(" mShowAODFpIcon: " + mShowAODFpIcon);
pw.println(" mIsDozing: " + mIsDozing);
pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
@@ -357,17 +395,57 @@
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState));
pw.println(" mQsExpanded: " + mQsExpanded);
+ pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
if (mView != null) {
mView.dump(fd, pw, args);
}
}
+ /** Every minute, update the aod icon's burn in offset */
+ public void dozeTimeTick() {
+ updateBurnInOffsets();
+ }
+
+ private void updateBurnInOffsets() {
+ float offsetX = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
+ - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
+ float offsetY = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
+ - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
+ float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
+
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
+
+ private void updateIsUdfpsEnrolled() {
+ boolean wasUdfpsSupported = mUdfpsSupported;
+ boolean wasUdfpsEnrolled = mUdfpsEnrolled;
+
+ mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+ mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
+ updateVisibility();
+ }
+ }
+
private StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ mInterpolatedDarkAmount = eased;
+ updateBurnInOffsets();
+ }
+
+ @Override
public void onDozingChanged(boolean isDozing) {
mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateIsUdfpsEnrolled();
updateVisibility();
}
@@ -441,7 +519,7 @@
mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
KeyguardUpdateMonitor.getCurrentUser());
}
- mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ updateIsUdfpsEnrolled();
updateVisibility();
}
@@ -487,8 +565,7 @@
// intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
// MotionEvent.ACTION_UP (see #onTouchEvent)
- mDownDetected = true;
- if (mVibrator != null) {
+ if (mVibrator != null && !mDownDetected) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
@@ -496,6 +573,8 @@
"lockIcon-onDown",
VIBRATION_SONIFICATION_ATTRIBUTES);
}
+
+ mDownDetected = true;
return true;
}
@@ -553,6 +632,9 @@
mAuthRippleController.showRipple(FINGERPRINT);
}
updateVisibility();
+ if (mOnGestureDetectedRunnable != null) {
+ mOnGestureDetectedRunnable.run();
+ }
mKeyguardViewController.showBouncer(/* scrim */ true);
return true;
}
@@ -563,16 +645,18 @@
* in a 'clickable' state
* @return whether to intercept the touch event
*/
- public boolean onTouchEvent(MotionEvent event) {
+ public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
- && mView.getVisibility() == View.VISIBLE) {
+ && (mView.getVisibility() == View.VISIBLE
+ || mAodFp.getVisibility() == View.VISIBLE)) {
+ mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
// we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP
// or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV
// after the lock icon disappears on device entry
- if (mDownDetected && mDetectedLongPress) {
+ if (mDownDetected) {
if (event.getAction() == MotionEvent.ACTION_CANCEL
|| event.getAction() == MotionEvent.ACTION_UP) {
mDownDetected = false;
@@ -596,7 +680,7 @@
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
public void onAllAuthenticatorsRegistered() {
- mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+ updateIsUdfpsEnrolled();
updateConfiguration();
}
};
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
index 3a0357d..4331f52 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -16,7 +16,8 @@
package com.android.keyguard.dagger;
-import com.android.systemui.statusbar.phone.UserAvatarView;
+import android.widget.FrameLayout;
+
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import dagger.BindsInstance;
@@ -31,8 +32,7 @@
/** Simple factory for {@link KeyguardUserSwitcherComponent}. */
@Subcomponent.Factory
interface Factory {
- KeyguardQsUserSwitchComponent build(
- @BindsInstance UserAvatarView userAvatarView);
+ KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer);
}
/** Builds a {@link KeyguardQsUserSwitchController}. */
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index 17818cd..59d9aff 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -19,8 +19,9 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.util.MathUtils.constrain;
import static android.util.MathUtils.sq;
+import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.systemBars;
import static java.util.Objects.requireNonNull;
@@ -41,7 +42,6 @@
import android.graphics.drawable.LayerDrawable;
import android.os.Handler;
import android.os.Looper;
-import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -95,7 +95,6 @@
private boolean mIsShowing;
private boolean mIsDownInEnlargedTouchArea;
private boolean mIsDragging = false;
- private boolean mImeVisibility;
@Alignment
private int mAlignment;
@SizeType
@@ -108,8 +107,10 @@
private int mRadiusType;
private int mMargin;
private int mPadding;
- private int mScreenHeight;
- private int mScreenWidth;
+ // The display width excludes the window insets of the system bar and display cutout.
+ private int mDisplayHeight;
+ // The display Height excludes the window insets of the system bar and display cutout.
+ private int mDisplayWidth;
private int mIconWidth;
private int mIconHeight;
private int mInset;
@@ -118,6 +119,8 @@
private int mRelativeToPointerDownX;
private int mRelativeToPointerDownY;
private float mRadius;
+ private final Rect mDisplayInsetsRect = new Rect();
+ private final Rect mImeInsetsRect = new Rect();
private final Position mPosition;
private float mSquareScaledTouchSlop;
private final Configuration mLastConfiguration;
@@ -506,9 +509,21 @@
}
private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
- final boolean currentImeVisibility = insets.isVisible(ime());
- if (currentImeVisibility != mImeVisibility) {
- mImeVisibility = currentImeVisibility;
+ final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ final Rect displayWindowInsetsRect = getDisplayInsets(windowMetrics).toRect();
+ if (!displayWindowInsetsRect.equals(mDisplayInsetsRect)) {
+ updateDisplaySizeWith(windowMetrics);
+ updateLocationWith(mPosition);
+ }
+
+ final Rect imeInsetsRect = windowMetrics.getWindowInsets().getInsets(ime()).toRect();
+ if (!imeInsetsRect.equals(mImeInsetsRect)) {
+ if (isImeVisible(imeInsetsRect)) {
+ mImeInsetsRect.set(imeInsetsRect);
+ } else {
+ mImeInsetsRect.setEmpty();
+ }
+
updateLocationWith(mPosition);
}
@@ -520,6 +535,11 @@
|| (side == Alignment.LEFT && downX > currentRawX);
}
+ private boolean isImeVisible(Rect imeInsetsRect) {
+ return imeInsetsRect.left != 0 || imeInsetsRect.top != 0 || imeInsetsRect.right != 0
+ || imeInsetsRect.bottom != 0;
+ }
+
private boolean hasExceededTouchSlop(int startX, int startY, int endX, int endY) {
return (sq(endX - startX) + sq(endY - startY)) > mSquareScaledTouchSlop;
}
@@ -546,9 +566,9 @@
private void updateDimensions() {
final Resources res = getResources();
- final DisplayMetrics dm = res.getDisplayMetrics();
- mScreenWidth = dm.widthPixels;
- mScreenHeight = dm.heightPixels;
+
+ updateDisplaySizeWith(mWindowManager.getCurrentWindowMetrics());
+
mMargin =
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
mInset =
@@ -560,6 +580,15 @@
updateItemViewDimensionsWith(mSizeType);
}
+ private void updateDisplaySizeWith(WindowMetrics metrics) {
+ final Rect displayBounds = metrics.getBounds();
+ final Insets displayInsets = getDisplayInsets(metrics);
+ mDisplayInsetsRect.set(displayInsets.toRect());
+ displayBounds.inset(displayInsets);
+ mDisplayWidth = displayBounds.width();
+ mDisplayHeight = displayBounds.height();
+ }
+
private void updateItemViewDimensionsWith(@SizeType int sizeType) {
final Resources res = getResources();
final int paddingResId =
@@ -684,11 +713,11 @@
}
private int getMaxWindowX() {
- return mScreenWidth - getMarginStartEndWith(mLastConfiguration) - getLayoutWidth();
+ return mDisplayWidth - getMarginStartEndWith(mLastConfiguration) - getLayoutWidth();
}
private int getMaxWindowY() {
- return mScreenHeight - getWindowHeight();
+ return mDisplayHeight - getWindowHeight();
}
private InstantInsetLayerDrawable getMenuLayerDrawable() {
@@ -699,8 +728,13 @@
return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
}
+ private Insets getDisplayInsets(WindowMetrics metrics) {
+ return metrics.getWindowInsets().getInsetsIgnoringVisibility(
+ systemBars() | displayCutout());
+ }
+
/**
- * Updates the floating menu to be fixed at the side of the screen.
+ * Updates the floating menu to be fixed at the side of the display.
*/
private void updateLocationWith(Position position) {
final @Alignment int alignment = transformToAlignment(position.getPercentageX());
@@ -716,15 +750,9 @@
* @return the moving interval if they overlap each other, otherwise 0.
*/
private int getInterval() {
- if (!mImeVisibility) {
- return 0;
- }
-
- final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- final Insets imeInsets = windowMetrics.getWindowInsets().getInsets(
- ime() | navigationBars());
- final int imeY = mScreenHeight - imeInsets.bottom;
- final int layoutBottomY = mCurrentLayoutParams.y + getWindowHeight();
+ final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
+ final int imeY = mDisplayHeight - mImeInsetsRect.bottom;
+ final int layoutBottomY = currentLayoutY + getWindowHeight();
return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0;
}
@@ -855,11 +883,12 @@
@VisibleForTesting
Rect getAvailableBounds() {
- return new Rect(0, 0, mScreenWidth - getWindowWidth(), mScreenHeight - getWindowHeight());
+ return new Rect(0, 0, mDisplayWidth - getWindowWidth(),
+ mDisplayHeight - getWindowHeight());
}
private int getMaxLayoutHeight() {
- return mScreenHeight - mMargin * 2;
+ return mDisplayHeight - mMargin * 2;
}
private int getLayoutWidth() {
@@ -875,7 +904,7 @@
}
private int getWindowHeight() {
- return Math.min(mScreenHeight, mMargin * 2 + getLayoutHeight());
+ return Math.min(mDisplayHeight, mMargin * 2 + getLayoutHeight());
}
private void setSystemGestureExclusion() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
index 947466f..adb10f0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
@@ -1,3 +1,4 @@
set noparent
include /services/core/java/com/android/server/biometrics/OWNERS
+beverlyt@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 63fb8e2..594a642 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -41,7 +41,6 @@
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.media.AudioAttributes;
import android.os.Handler;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -65,7 +64,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -73,6 +71,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -95,7 +94,7 @@
* controls/manages all UDFPS sensors. In other words, a single controller is registered with
* {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
* as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
- * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have
+ * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have
* {@code sensorId} parameters.
*/
@SuppressWarnings("deprecation")
@@ -119,9 +118,7 @@
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final DumpManager mDumpManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@Nullable private final Vibrator mVibrator;
- @NonNull private final Handler mMainHandler;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -131,6 +128,8 @@
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final SystemClock mSystemClock;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -245,7 +244,7 @@
final UdfpsEnrollHelper enrollHelper;
if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
|| reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) {
- enrollHelper = new UdfpsEnrollHelper(mContext, reason);
+ enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
} else {
enrollHelper = null;
}
@@ -519,7 +518,6 @@
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -533,11 +531,11 @@
@NonNull DisplayManager displayManager,
@Main Handler mainHandler,
@NonNull ConfigurationController configurationController,
- @NonNull SystemClock systemClock) {
+ @NonNull SystemClock systemClock,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContext = context;
mExecution = execution;
// TODO (b/185124905): inject main handler and vibrator once done prototyping
- mMainHandler = new Handler(Looper.getMainLooper());
mVibrator = vibrator;
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -551,7 +549,6 @@
mKeyguardViewManager = statusBarKeyguardViewManager;
mDumpManager = dumpManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardViewMediator = keyguardViewMediator;
mFalsingManager = falsingManager;
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
@@ -562,6 +559,7 @@
mKeyguardBypassController = keyguardBypassController;
mConfigurationController = configurationController;
mSystemClock = systemClock;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -661,6 +659,20 @@
}
}
+ private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) {
+ if (!(animation instanceof UdfpsKeyguardViewController)) {
+ // always rotate view if we're not on the keyguard
+ return true;
+ }
+
+ // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded
+ if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) {
+ return false;
+ }
+
+ return true;
+ }
+
private WindowManager.LayoutParams computeLayoutParams(
@Nullable UdfpsAnimationViewController animation) {
final int paddingX = animation != null ? animation.getPaddingX() : 0;
@@ -685,9 +697,11 @@
// Transform dimensions if the device is in landscape mode
switch (mContext.getDisplay().getRotation()) {
case Surface.ROTATION_90:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_90");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_90");
}
mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius
- paddingX;
@@ -696,9 +710,11 @@
break;
case Surface.ROTATION_270:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_270");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_270");
}
mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius
- paddingX;
@@ -795,13 +811,12 @@
mStatusBarOptional,
mKeyguardViewManager,
mKeyguardUpdateMonitor,
- mFgExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
mSystemClock,
mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
this
);
case BiometricOverlayConstants.REASON_AUTH_BP:
@@ -866,6 +881,10 @@
return;
}
+ if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ return;
+ }
+
mAodInterruptRunnable = () -> {
mIsAodInterruptActive = true;
// Since the sensor that triggers the AOD interrupt doesn't provide
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index d407756..2034ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -26,11 +28,17 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.TypedValue;
import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
/**
@@ -39,10 +47,20 @@
public class UdfpsEnrollDrawable extends UdfpsDrawable {
private static final String TAG = "UdfpsAnimationEnroll";
- private static final long ANIM_DURATION = 800;
+ private static final long HINT_COLOR_ANIM_DELAY_MS = 233L;
+ private static final long HINT_COLOR_ANIM_DURATION_MS = 517L;
+ private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L;
+ private static final long TARGET_ANIM_DURATION_LONG = 800L;
+ private static final long TARGET_ANIM_DURATION_SHORT = 600L;
// 1 + SCALE_MAX is the maximum that the moving target will animate to
private static final float SCALE_MAX = 0.25f;
+ private static final float HINT_PADDING_DP = 10f;
+ private static final float HINT_MAX_WIDTH_DP = 6f;
+ private static final float HINT_ANGLE = 40f;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
@NonNull private final Drawable mMovingTargetFpIcon;
@NonNull private final Paint mSensorOutlinePaint;
@NonNull private final Paint mBlueFill;
@@ -51,17 +69,41 @@
@Nullable private UdfpsEnrollHelper mEnrollHelper;
// Moving target animator set
- @Nullable AnimatorSet mAnimatorSet;
+ @Nullable AnimatorSet mTargetAnimatorSet;
// Moving target location
float mCurrentX;
float mCurrentY;
// Moving target size
float mCurrentScale = 1.f;
+ @ColorInt private final int mHintColorFaded;
+ @ColorInt private final int mHintColorHighlight;
+ private final float mHintMaxWidthPx;
+ private final float mHintPaddingPx;
+
+ @NonNull private final Animator.AnimatorListener mTargetAnimListener;
+
+ private boolean mShouldShowTipHint = false;
+ @NonNull private final Paint mTipHintPaint;
+ @Nullable private AnimatorSet mTipHintAnimatorSet;
+ @Nullable private ValueAnimator mTipHintColorAnimator;
+ @Nullable private ValueAnimator mTipHintWidthAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener;
+ @NonNull private final Animator.AnimatorListener mTipHintPulseListener;
+
+ private boolean mShouldShowEdgeHint = false;
+ @NonNull private final Paint mEdgeHintPaint;
+ @Nullable private AnimatorSet mEdgeHintAnimatorSet;
+ @Nullable private ValueAnimator mEdgeHintColorAnimator;
+ @Nullable private ValueAnimator mEdgeHintWidthAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener;
+ @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener;
+
UdfpsEnrollDrawable(@NonNull Context context) {
super(context);
-
mSensorOutlinePaint = new Paint(0 /* flags */);
mSensorOutlinePaint.setAntiAlias(true);
mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
@@ -78,6 +120,117 @@
mMovingTargetFpIcon.mutate();
mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
+
+ mHintColorFaded = getHintColorFaded(context);
+ mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
+ mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP);
+ mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP);
+
+ mTargetAnimListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateTipHintVisibility();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+
+ mTipHintPaint = new Paint(0 /* flags */);
+ mTipHintPaint.setAntiAlias(true);
+ mTipHintPaint.setColor(mHintColorFaded);
+ mTipHintPaint.setStyle(Paint.Style.STROKE);
+ mTipHintPaint.setStrokeCap(Paint.Cap.ROUND);
+ mTipHintPaint.setStrokeWidth(0f);
+ mTipHintColorUpdateListener = animation -> {
+ mTipHintPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mTipHintWidthUpdateListener = animation -> {
+ mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mTipHintPulseListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHandler.postDelayed(() -> {
+ mTipHintColorAnimator =
+ ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded);
+ mTipHintColorAnimator.setInterpolator(new LinearInterpolator());
+ mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+ mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+ mTipHintColorAnimator.start();
+ }, HINT_COLOR_ANIM_DELAY_MS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+
+ mEdgeHintPaint = new Paint(0 /* flags */);
+ mEdgeHintPaint.setAntiAlias(true);
+ mEdgeHintPaint.setColor(mHintColorFaded);
+ mEdgeHintPaint.setStyle(Paint.Style.STROKE);
+ mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND);
+ mEdgeHintPaint.setStrokeWidth(0f);
+ mEdgeHintColorUpdateListener = animation -> {
+ mEdgeHintPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mEdgeHintWidthUpdateListener = animation -> {
+ mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mEdgeHintPulseListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHandler.postDelayed(() -> {
+ mEdgeHintColorAnimator =
+ ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded);
+ mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator());
+ mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+ mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+ mEdgeHintColorAnimator.start();
+ }, HINT_COLOR_ANIM_DELAY_MS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+ }
+
+ @ColorInt
+ private static int getHintColorFaded(@NonNull Context context) {
+ final TypedValue tv = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
+ final int alpha = (int) (tv.getFloat() * 255f);
+
+ final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+ final TypedArray ta = context.obtainStyledAttributes(attrs);
+ try {
+ @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled));
+ return ColorUtils.setAlphaComponent(color, alpha);
+ } finally {
+ ta.recycle();
+ }
}
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
@@ -98,41 +251,154 @@
}
void onEnrollmentProgress(int remaining, int totalSteps) {
- if (mEnrollHelper.isCenterEnrollmentComplete()) {
- if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
- mAnimatorSet.end();
+ if (mEnrollHelper == null) {
+ return;
+ }
+
+ if (!mEnrollHelper.isCenterEnrollmentStage()) {
+ if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
+ mTargetAnimatorSet.end();
}
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
+ if (mCurrentX != point.x || mCurrentY != point.y) {
+ final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
+ x.addUpdateListener(animation -> {
+ mCurrentX = (float) animation.getAnimatedValue();
+ invalidateSelf();
+ });
- final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
- x.addUpdateListener(animation -> {
- mCurrentX = (float) animation.getAnimatedValue();
- invalidateSelf();
- });
+ final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
+ y.addUpdateListener(animation -> {
+ mCurrentY = (float) animation.getAnimatedValue();
+ invalidateSelf();
+ });
- final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
- y.addUpdateListener(animation -> {
- mCurrentY = (float) animation.getAnimatedValue();
- invalidateSelf();
- });
+ final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
+ final long duration = isMovingToCenter
+ ? TARGET_ANIM_DURATION_SHORT
+ : TARGET_ANIM_DURATION_LONG;
- final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
- scale.setDuration(ANIM_DURATION);
- scale.addUpdateListener(animation -> {
- // Grow then shrink
- mCurrentScale = 1 +
- SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
- invalidateSelf();
- });
+ final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
+ scale.setDuration(duration);
+ scale.addUpdateListener(animation -> {
+ // Grow then shrink
+ mCurrentScale = 1
+ + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
+ invalidateSelf();
+ });
- mAnimatorSet = new AnimatorSet();
+ mTargetAnimatorSet = new AnimatorSet();
- mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
- mAnimatorSet.setDuration(ANIM_DURATION);
- mAnimatorSet.playTogether(x, y, scale);
- mAnimatorSet.start();
+ mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mTargetAnimatorSet.setDuration(duration);
+ mTargetAnimatorSet.addListener(mTargetAnimListener);
+ mTargetAnimatorSet.playTogether(x, y, scale);
+ mTargetAnimatorSet.start();
+ } else {
+ updateTipHintVisibility();
+ }
+ } else {
+ updateTipHintVisibility();
}
+
+ updateEdgeHintVisibility();
+ }
+
+ private void updateTipHintVisibility() {
+ final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
+ if (mShouldShowTipHint == shouldShow) {
+ return;
+ }
+ mShouldShowTipHint = shouldShow;
+
+ if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) {
+ mTipHintWidthAnimator.cancel();
+ }
+
+ final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+ mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth);
+ mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener);
+
+ if (shouldShow) {
+ startTipHintPulseAnimation();
+ } else {
+ mTipHintWidthAnimator.start();
+ }
+ }
+
+ private void updateEdgeHintVisibility() {
+ final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
+ if (mShouldShowEdgeHint == shouldShow) {
+ return;
+ }
+ mShouldShowEdgeHint = shouldShow;
+
+ if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) {
+ mEdgeHintWidthAnimator.cancel();
+ }
+
+ final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+ mEdgeHintWidthAnimator =
+ ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth);
+ mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener);
+
+ if (shouldShow) {
+ startEdgeHintPulseAnimation();
+ } else {
+ mEdgeHintWidthAnimator.start();
+ }
+ }
+
+ private void startTipHintPulseAnimation() {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) {
+ mTipHintAnimatorSet.cancel();
+ }
+ if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) {
+ mTipHintColorAnimator.cancel();
+ }
+
+ mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight);
+ mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+ mTipHintColorAnimator.addListener(mTipHintPulseListener);
+
+ mTipHintAnimatorSet = new AnimatorSet();
+ mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator);
+ mTipHintAnimatorSet.start();
+ }
+
+ private void startEdgeHintPulseAnimation() {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) {
+ mEdgeHintAnimatorSet.cancel();
+ }
+ if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) {
+ mEdgeHintColorAnimator.cancel();
+ }
+
+ mEdgeHintColorAnimator =
+ ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight);
+ mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+ mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener);
+
+ mEdgeHintAnimatorSet = new AnimatorSet();
+ mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator);
+ mEdgeHintAnimatorSet.start();
+ }
+
+ private boolean isTipHintVisible() {
+ return mTipHintPaint.getStrokeWidth() > 0f;
+ }
+
+ private boolean isEdgeHintVisible() {
+ return mEdgeHintPaint.getStrokeWidth() > 0f;
}
@Override
@@ -142,7 +408,7 @@
}
// Draw moving target
- if (mEnrollHelper.isCenterEnrollmentComplete()) {
+ if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
canvas.save();
canvas.translate(mCurrentX, mCurrentY);
@@ -162,6 +428,59 @@
mFingerprintDrawable.setAlpha(mAlpha);
mSensorOutlinePaint.setAlpha(mAlpha);
}
+
+ // Draw the finger tip or edges hint.
+ if (isTipHintVisible() || isEdgeHintVisible()) {
+ canvas.save();
+
+ // Make arcs start from the top, rather than the right.
+ canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+
+ final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f;
+ final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f;
+ final float hintXOffset = halfSensorWidth + mHintPaddingPx;
+ final float hintYOffset = halfSensorHeight + mHintPaddingPx;
+
+ if (isTipHintVisible()) {
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mTipHintPaint);
+ }
+
+ if (isEdgeHintVisible()) {
+ // Draw right edge hint.
+ canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mEdgeHintPaint);
+
+ // Draw left edge hint.
+ canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY());
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mEdgeHintPaint);
+ }
+
+ canvas.restore();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index c6d2192..d5c763d3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.graphics.PointF;
import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
@@ -44,16 +45,14 @@
private static final String NEW_COORDS_OVERRIDE =
"com.android.systemui.biometrics.UdfpsNewCoords";
- // Enroll with two center touches before going to guided enrollment
- private static final int NUM_CENTER_TOUCHES = 2;
-
interface Listener {
void onEnrollmentProgress(int remaining, int totalSteps);
+ void onEnrollmentHelp(int remaining, int totalSteps);
void onLastStepAcquired();
- void onEnrollmentHelp();
}
@NonNull private final Context mContext;
+ @NonNull private final FingerprintManager mFingerprintManager;
// IUdfpsOverlayController reason
private final int mEnrollReason;
private final boolean mAccessibilityEnabled;
@@ -66,10 +65,15 @@
// interface makes no promises about monotonically increasing by one each time.
private int mLocationsEnrolled = 0;
+ private int mCenterTouchCount = 0;
+
@Nullable Listener mListener;
- public UdfpsEnrollHelper(@NonNull Context context, int reason) {
+ public UdfpsEnrollHelper(@NonNull Context context,
+ @NonNull FingerprintManager fingerprintManager, int reason) {
+
mContext = context;
+ mFingerprintManager = fingerprintManager;
mEnrollReason = reason;
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -118,6 +122,14 @@
}
}
+ int getStageCount() {
+ return mFingerprintManager.getEnrollStageCount();
+ }
+
+ int getStageThresholdSteps(int totalSteps, int stageIndex) {
+ return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
+ }
+
boolean shouldShowProgressBar() {
return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
}
@@ -129,6 +141,9 @@
if (remaining != mRemainingSteps) {
mLocationsEnrolled++;
+ if (isCenterEnrollmentStage()) {
+ mCenterTouchCount++;
+ }
}
mRemainingSteps = remaining;
@@ -140,7 +155,7 @@
void onEnrollmentHelp() {
if (mListener != null) {
- mListener.onEnrollmentHelp();
+ mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
}
}
@@ -155,19 +170,41 @@
}
}
- boolean isCenterEnrollmentComplete() {
+ boolean isCenterEnrollmentStage() {
if (mTotalSteps == -1 || mRemainingSteps == -1) {
- return false;
- } else if (mAccessibilityEnabled) {
+ return true;
+ }
+ return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
+ }
+
+ boolean isGuidedEnrollmentStage() {
+ if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
return false;
}
- final int stepsEnrolled = mTotalSteps - mRemainingSteps;
- return stepsEnrolled >= NUM_CENTER_TOUCHES;
+ final int progressSteps = mTotalSteps - mRemainingSteps;
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 1);
+ }
+
+ boolean isTipEnrollmentStage() {
+ if (mTotalSteps == -1 || mRemainingSteps == -1) {
+ return false;
+ }
+ final int progressSteps = mTotalSteps - mRemainingSteps;
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 2);
+ }
+
+ boolean isEdgeEnrollmentStage() {
+ if (mTotalSteps == -1 || mRemainingSteps == -1) {
+ return false;
+ }
+ return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
}
@NonNull
PointF getNextGuidedEnrollmentPoint() {
- if (mAccessibilityEnabled) {
+ if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
return new PointF(0f, 0f);
}
@@ -177,7 +214,7 @@
SCALE_OVERRIDE, SCALE,
UserHandle.USER_CURRENT);
}
- final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES;
+ final int index = mLocationsEnrolled - mCenterTouchCount;
final PointF originalPoint = mGuidedEnrollmentPoints
.get(index % mGuidedEnrollmentPoints.size());
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 373d17c8..b2a5409 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -16,163 +16,129 @@
package com.android.systemui.biometrics;
-import android.animation.ArgbEvaluator;
-import android.animation.ValueAnimator;
-import android.annotation.ColorInt;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
-import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.Log;
-import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.systemui.R;
+import java.util.ArrayList;
+import java.util.List;
/**
* UDFPS enrollment progress bar.
*/
public class UdfpsEnrollProgressBarDrawable extends Drawable {
+ private static final String TAG = "UdfpsProgressBar";
- private static final String TAG = "UdfpsEnrollProgressBarDrawable";
-
- private static final float PROGRESS_BAR_THICKNESS_DP = 12;
+ private static final float SEGMENT_GAP_ANGLE = 12f;
@NonNull private final Context mContext;
- @NonNull private final Paint mBackgroundCirclePaint;
- @NonNull private final Paint mProgressPaint;
- @Nullable private ValueAnimator mProgressAnimator;
- @Nullable private ValueAnimator mProgressShowingHelpAnimator;
- @Nullable private ValueAnimator mProgressHidingHelpAnimator;
- @ColorInt private final int mProgressColor;
- @ColorInt private final int mProgressHelpColor;
- private final int mShortAnimationDuration;
- private float mProgress;
- private int mRotation; // After last step, rotate the progress bar once
- private boolean mLastStepAcquired;
+ @Nullable private UdfpsEnrollHelper mEnrollHelper;
+ @NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
+ private int mTotalSteps = 1;
+ private int mProgressSteps = 0;
+ private boolean mIsShowingHelp = false;
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
mContext = context;
-
- mShortAnimationDuration = context.getResources()
- .getInteger(com.android.internal.R.integer.config_shortAnimTime);
- mProgressColor = context.getColor(R.color.udfps_enroll_progress);
- mProgressHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
-
- mBackgroundCirclePaint = new Paint();
- mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
- mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled));
- mBackgroundCirclePaint.setAntiAlias(true);
- mBackgroundCirclePaint.setStyle(Paint.Style.STROKE);
-
- // Background circle color + alpha
- TypedArray tc = context.obtainStyledAttributes(
- new int[] {android.R.attr.colorControlNormal});
- int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor());
- mBackgroundCirclePaint.setColor(tintColor);
- tc.recycle();
- TypedValue alpha = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
- mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255));
-
- // Progress should not be color extracted
- mProgressPaint = new Paint();
- mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
- mProgressPaint.setColor(mProgressColor);
- mProgressPaint.setAntiAlias(true);
- mProgressPaint.setStyle(Paint.Style.STROKE);
- mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
}
- void setEnrollmentProgress(int remaining, int totalSteps) {
- // Add one so that the first steps actually changes progress, but also so that the last
- // step ends at 1.0
- final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
- setEnrollmentProgress(progress);
- }
-
- private void setEnrollmentProgress(float progress) {
- if (mLastStepAcquired) {
- return;
- }
-
- long animationDuration = mShortAnimationDuration;
-
- hideEnrollmentHelp();
-
- if (progress == 1.f) {
- animationDuration = 400;
- final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400);
- rotationAnimator.setDuration(animationDuration);
- rotationAnimator.addUpdateListener(animation -> {
- Log.d(TAG, "Rotation: " + mRotation);
- mRotation = (int) animation.getAnimatedValue();
- invalidateSelf();
- });
- rotationAnimator.start();
- }
-
- if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
- mProgressAnimator.cancel();
- }
-
- mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
- mProgressAnimator.setDuration(animationDuration);
- mProgressAnimator.addUpdateListener(animation -> {
- mProgress = (float) animation.getAnimatedValue();
+ void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
+ mEnrollHelper = enrollHelper;
+ if (enrollHelper != null) {
+ final int stageCount = enrollHelper.getStageCount();
+ mSegments = new ArrayList<>(stageCount);
+ float startAngle = SEGMENT_GAP_ANGLE / 2f;
+ final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
+ final Runnable invalidateRunnable = this::invalidateSelf;
+ for (int index = 0; index < stageCount; index++) {
+ mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
+ sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
+ startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
+ }
invalidateSelf();
- });
- mProgressAnimator.start();
+ }
+ }
+
+ void onEnrollmentProgress(int remaining, int totalSteps) {
+ mTotalSteps = totalSteps;
+ updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */);
+ }
+
+ void onEnrollmentHelp(int remaining, int totalSteps) {
+ updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */);
}
void onLastStepAcquired() {
- setEnrollmentProgress(1.f);
- mLastStepAcquired = true;
+ updateState(mTotalSteps, false /* isShowingHelp */);
}
- void onEnrollmentHelp() {
- if (mProgressShowingHelpAnimator != null || mProgressAnimator == null) {
- return; // already showing or at 0% (no progress bar visible)
- }
-
- if (mProgressHidingHelpAnimator != null && mProgressHidingHelpAnimator.isRunning()) {
- mProgressHidingHelpAnimator.cancel();
- }
- mProgressHidingHelpAnimator = null;
-
- mProgressShowingHelpAnimator = getProgressColorAnimator(
- mProgressPaint.getColor(), mProgressHelpColor);
- mProgressShowingHelpAnimator.start();
+ private static int getProgressSteps(int remaining, int totalSteps) {
+ // Show some progress for the initial touch.
+ return Math.max(1, totalSteps - remaining);
}
- private void hideEnrollmentHelp() {
- if (mProgressHidingHelpAnimator != null || mProgressShowingHelpAnimator == null) {
- return; // already hidden or help never shown
- }
-
- if (mProgressShowingHelpAnimator != null && mProgressShowingHelpAnimator.isRunning()) {
- mProgressShowingHelpAnimator.cancel();
- }
- mProgressShowingHelpAnimator = null;
-
- mProgressHidingHelpAnimator = getProgressColorAnimator(
- mProgressPaint.getColor(), mProgressColor);
- mProgressHidingHelpAnimator.start();
+ private void updateState(int progressSteps, boolean isShowingHelp) {
+ updateProgress(progressSteps);
+ updateFillColor(isShowingHelp);
}
- private ValueAnimator getProgressColorAnimator(@ColorInt int from, @ColorInt int to) {
- final ValueAnimator animator = ValueAnimator.ofObject(
- ArgbEvaluator.getInstance(), from, to);
- animator.setDuration(mShortAnimationDuration);
- animator.addUpdateListener(animation -> {
- mProgressPaint.setColor((int) animation.getAnimatedValue());
- });
- return animator;
+ private void updateProgress(int progressSteps) {
+ if (mProgressSteps == progressSteps) {
+ return;
+ }
+ mProgressSteps = progressSteps;
+
+ if (mEnrollHelper == null) {
+ Log.e(TAG, "updateState: UDFPS enroll helper was null");
+ return;
+ }
+
+ int index = 0;
+ int prevThreshold = 0;
+ while (index < mSegments.size()) {
+ final UdfpsEnrollProgressBarSegment segment = mSegments.get(index);
+ final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index);
+ if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) {
+ segment.updateProgress(1f);
+ break;
+ } else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) {
+ final int relativeSteps = progressSteps - prevThreshold;
+ final int relativeThreshold = thresholdSteps - prevThreshold;
+ final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
+ segment.updateProgress(segmentProgress);
+ break;
+ }
+
+ index++;
+ prevThreshold = thresholdSteps;
+ }
+
+ if (progressSteps >= mTotalSteps) {
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.startCompletionAnimation();
+ }
+ } else {
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.cancelCompletionAnimation();
+ }
+ }
+ }
+
+ private void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.updateFillColor(isShowingHelp);
+ }
}
@Override
@@ -180,43 +146,22 @@
canvas.save();
// Progress starts from the top, instead of the right
- canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY());
+ canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
- // Progress bar "background track"
- final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2;
- canvas.drawArc(halfPaddingPx,
- halfPaddingPx,
- getBounds().right - halfPaddingPx,
- getBounds().bottom - halfPaddingPx,
- 0,
- 360,
- false,
- mBackgroundCirclePaint
- );
-
- final float progress = 360.f * mProgress;
- // Progress
- canvas.drawArc(halfPaddingPx,
- halfPaddingPx,
- getBounds().right - halfPaddingPx,
- getBounds().bottom - halfPaddingPx,
- 0,
- progress,
- false,
- mProgressPaint
- );
+ // Draw each of the enroll segments.
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.draw(canvas);
+ }
canvas.restore();
}
@Override
public void setAlpha(int alpha) {
-
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
-
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
new file mode 100644
index 0000000..bd6ab44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * A single segment of the UDFPS enrollment progress bar.
+ */
+public class UdfpsEnrollProgressBarSegment {
+ private static final String TAG = "UdfpsProgressBarSegment";
+
+ private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
+ private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
+ private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
+ private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
+
+ private static final float STROKE_WIDTH_DP = 12f;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @NonNull private final Rect mBounds;
+ @NonNull private final Runnable mInvalidateRunnable;
+ private final float mStartAngle;
+ private final float mSweepAngle;
+ private final float mMaxOverSweepAngle;
+ private final float mStrokeWidthPx;
+ @ColorInt private final int mProgressColor;
+ @ColorInt private final int mHelpColor;
+
+ @NonNull private final Paint mBackgroundPaint;
+ @NonNull private final Paint mProgressPaint;
+
+ private float mProgress = 0f;
+ private float mAnimatedProgress = 0f;
+ @Nullable private ValueAnimator mProgressAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
+
+ private boolean mIsShowingHelp = false;
+ @Nullable private ValueAnimator mFillColorAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
+
+ private float mOverSweepAngle = 0f;
+ @Nullable private ValueAnimator mOverSweepAnimator;
+ @Nullable private ValueAnimator mOverSweepReverseAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener;
+ @NonNull private final Runnable mOverSweepAnimationRunnable;
+
+ public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds,
+ float startAngle, float sweepAngle, float maxOverSweepAngle,
+ @NonNull Runnable invalidateRunnable) {
+
+ mBounds = bounds;
+ mInvalidateRunnable = invalidateRunnable;
+ mStartAngle = startAngle;
+ mSweepAngle = sweepAngle;
+ mMaxOverSweepAngle = maxOverSweepAngle;
+ mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
+ mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+ mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+
+ mBackgroundPaint = new Paint();
+ mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
+ mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setStyle(Paint.Style.STROKE);
+ mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ // Background paint color + alpha
+ final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+ final TypedArray ta = context.obtainStyledAttributes(attrs);
+ @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor());
+ mBackgroundPaint.setColor(tintColor);
+ ta.recycle();
+ TypedValue alpha = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
+ mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
+
+ // Progress should not be color extracted
+ mProgressPaint = new Paint();
+ mProgressPaint.setStrokeWidth(mStrokeWidthPx);
+ mProgressPaint.setColor(mProgressColor);
+ mProgressPaint.setAntiAlias(true);
+ mProgressPaint.setStyle(Paint.Style.STROKE);
+ mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mProgressUpdateListener = animation -> {
+ mAnimatedProgress = (float) animation.getAnimatedValue();
+ mInvalidateRunnable.run();
+ };
+
+ mFillColorUpdateListener = animation -> {
+ mProgressPaint.setColor((int) animation.getAnimatedValue());
+ mInvalidateRunnable.run();
+ };
+
+ mOverSweepUpdateListener = animation -> {
+ mOverSweepAngle = (float) animation.getAnimatedValue();
+ mInvalidateRunnable.run();
+ };
+ mOverSweepAnimationRunnable = () -> {
+ if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+ mOverSweepAnimator.cancel();
+ }
+ mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle);
+ mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS);
+ mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener);
+ mOverSweepAnimator.start();
+ };
+ }
+
+ /**
+ * Draws this segment to the given canvas.
+ */
+ public void draw(@NonNull Canvas canvas) {
+ final float halfPaddingPx = mStrokeWidthPx / 2f;
+
+ if (mAnimatedProgress < 1f) {
+ // Draw the unfilled background color of the segment.
+ canvas.drawArc(
+ halfPaddingPx,
+ halfPaddingPx,
+ mBounds.right - halfPaddingPx,
+ mBounds.bottom - halfPaddingPx,
+ mStartAngle,
+ mSweepAngle,
+ false /* useCenter */,
+ mBackgroundPaint);
+ }
+
+ if (mAnimatedProgress > 0f) {
+ // Draw the filled progress portion of the segment.
+ canvas.drawArc(
+ halfPaddingPx,
+ halfPaddingPx,
+ mBounds.right - halfPaddingPx,
+ mBounds.bottom - halfPaddingPx,
+ mStartAngle,
+ mSweepAngle * mAnimatedProgress + mOverSweepAngle,
+ false /* useCenter */,
+ mProgressPaint);
+ }
+ }
+
+ /**
+ * @return The fill progress of this segment, in the range [0, 1]. If fill progress is being
+ * animated, returns the value it is animating to.
+ */
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Updates the fill progress of this segment, animating if necessary.
+ *
+ * @param progress The new fill progress, in the range [0, 1].
+ */
+ public void updateProgress(float progress) {
+ updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS);
+ }
+
+ private void updateProgress(float progress, long animationDurationMs) {
+ if (mProgress == progress) {
+ return;
+ }
+ mProgress = progress;
+
+ if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
+ mProgressAnimator.cancel();
+ }
+
+ mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress);
+ mProgressAnimator.setDuration(animationDurationMs);
+ mProgressAnimator.addUpdateListener(mProgressUpdateListener);
+ mProgressAnimator.start();
+ }
+
+ /**
+ * Updates the fill color of this segment, animating if necessary.
+ *
+ * @param isShowingHelp Whether fill color should indicate that a help message is being shown.
+ */
+ public void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
+ mFillColorAnimator.cancel();
+ }
+
+ @ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor;
+ mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor);
+ mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
+ mFillColorAnimator.start();
+ }
+
+ /**
+ * Queues and runs the completion animation for this segment.
+ */
+ public void startCompletionAnimation() {
+ final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable);
+ if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) {
+ Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback
+ + ", mOverSweepAngle = " + mOverSweepAngle);
+ return;
+ }
+
+ // Reset sweep angle back to zero if the animation is being rolled back.
+ if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+ mOverSweepReverseAnimator.cancel();
+ mOverSweepAngle = 0f;
+ }
+
+ // Clear help color and start filling the segment if it isn't already.
+ if (mAnimatedProgress < 1f) {
+ updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
+ updateFillColor(false /* isShowingHelp */);
+ }
+
+ // Queue the animation to run after fill completes.
+ mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS);
+ }
+
+ /**
+ * Cancels (and reverses, if necessary) a queued or running completion animation.
+ */
+ public void cancelCompletionAnimation() {
+ // Cancel the animation if it's queued or running.
+ mHandler.removeCallbacks(mOverSweepAnimationRunnable);
+ if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+ mOverSweepAnimator.cancel();
+ }
+
+ // Roll back the animation if it has at least partially run.
+ if (mOverSweepAngle > 0f) {
+ if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+ mOverSweepReverseAnimator.cancel();
+ }
+
+ final float completion = mOverSweepAngle / mMaxOverSweepAngle;
+ final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion);
+ mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f);
+ mOverSweepReverseAnimator.setDuration(proratedDuration);
+ mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener);
+ mOverSweepReverseAnimator.start();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index c83006d..729838e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -73,23 +73,22 @@
}
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
+ mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
mFingerprintDrawable.setEnrollHelper(enrollHelper);
}
void onEnrollmentProgress(int remaining, int totalSteps) {
mHandler.post(() -> {
- mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps);
+ mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
});
}
- void onLastStepAcquired() {
- mHandler.post(() -> {
- mFingerprintProgressDrawable.onLastStepAcquired();
- });
+ void onEnrollmentHelp(int remaining, int totalSteps) {
+ mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
}
- void onEnrollmentHelp() {
- mHandler.post(mFingerprintProgressDrawable::onEnrollmentHelp);
+ void onLastStepAcquired() {
+ mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 33fbe7b..af7c352 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -35,21 +35,21 @@
@NonNull private final UdfpsEnrollHelper mEnrollHelper;
@NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
new UdfpsEnrollHelper.Listener() {
- @Override
- public void onEnrollmentProgress(int remaining, int totalSteps) {
- mView.onEnrollmentProgress(remaining, totalSteps);
- }
+ @Override
+ public void onEnrollmentProgress(int remaining, int totalSteps) {
+ mView.onEnrollmentProgress(remaining, totalSteps);
+ }
- @Override
- public void onLastStepAcquired() {
- mView.onLastStepAcquired();
- }
+ @Override
+ public void onEnrollmentHelp(int remaining, int totalSteps) {
+ mView.onEnrollmentHelp(remaining, totalSteps);
+ }
- @Override
- public void onEnrollmentHelp() {
- mView.onEnrollmentHelp();
- }
- };
+ @Override
+ public void onLastStepAcquired() {
+ mView.onLastStepAcquired();
+ }
+ };
protected UdfpsEnrollViewController(
@NonNull UdfpsEnrollView view,
@@ -81,7 +81,7 @@
@NonNull
@Override
public PointF getTouchTranslation() {
- if (!mEnrollHelper.isCenterEnrollmentComplete()) {
+ if (!mEnrollHelper.isGuidedEnrollmentStage()) {
return new PointF(0, 0);
} else {
return mEnrollHelper.getNextGuidedEnrollmentPoint();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 8ce4c3b..db93b26 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -26,36 +26,34 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Optional;
-
/**
* Class that coordinates non-HBM animations during keyguard authentication.
*/
public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final DelayableExecutor mExecutor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final SystemClock mSystemClock;
@NonNull private final KeyguardStateController mKeyguardStateController;
@NonNull private final UdfpsController mUdfpsController;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
private boolean mShowingUdfpsBouncer;
private boolean mUdfpsRequested;
@@ -82,24 +80,22 @@
@NonNull Optional<StatusBar> statusBarOptional,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DelayableExecutor mainDelayableExecutor,
@NonNull DumpManager dumpManager,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull LockscreenShadeTransitionController transitionController,
@NonNull ConfigurationController configurationController,
@NonNull SystemClock systemClock,
@NonNull KeyguardStateController keyguardStateController,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@NonNull UdfpsController udfpsController) {
super(view, statusBarStateController, statusBarOptional, dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mExecutor = mainDelayableExecutor;
- mKeyguardViewMediator = keyguardViewMediator;
mLockScreenShadeTransitionController = transitionController;
mConfigurationController = configurationController;
mSystemClock = systemClock;
mKeyguardStateController = keyguardStateController;
mUdfpsController = udfpsController;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
}
@Override
@@ -138,6 +134,7 @@
mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
+ mUnlockedScreenOffAnimationController.addCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -156,6 +153,7 @@
if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
}
+ mUnlockedScreenOffAnimationController.removeCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -428,4 +426,7 @@
updatePauseAuth();
}
};
+
+ private final UnlockedScreenOffAnimationController.Callback mUnlockedScreenOffCallback =
+ (linear, eased) -> mStateListener.onDozeAmountChanged(linear, eased);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 567d0cb..72da7f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -256,13 +256,13 @@
// Force animations when transitioning from a dialog to an activity
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
- if (keyguardStateController.isUnlocked()) {
+ if (keyguardStateController.isShowing()) {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
+ } else {
activityContext.startActivity(
intent,
ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle()
)
- } else {
- activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 657d924..845d7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -275,6 +275,13 @@
}
/**
+ * Appends sensor event dropped event to logs
+ */
+ public void traceSensorEventDropped(int sensorEvent, String reason) {
+ mLogger.logSensorEventDropped(sensorEvent, reason);
+ }
+
+ /**
* Appends pulse dropped event to logs
* @param reason why the pulse was dropped
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index fe37d49..dc18618 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -205,6 +205,15 @@
})
}
+ fun logSensorEventDropped(sensorEvent: Int, reason: String) {
+ buffer.log(TAG, INFO, {
+ int1 = sensorEvent
+ str1 = reason
+ }, {
+ "SensorEvent [$int1] dropped, reason=$str1"
+ })
+ }
+
fun logPulseDropped(reason: String) {
buffer.log(TAG, INFO, {
str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 455f3c0..756adca 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -41,6 +41,7 @@
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -92,6 +93,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
+ private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
@@ -179,7 +181,8 @@
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings, AuthController authController,
@Main DelayableExecutor mainExecutor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ KeyguardStateController keyguardStateController) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -198,6 +201,7 @@
mAuthController = authController;
mMainExecutor = mainExecutor;
mUiEventLogger = uiEventLogger;
+ mKeyguardStateController = keyguardStateController;
}
@Override
@@ -294,6 +298,7 @@
proximityCheckThenCall((result) -> {
if (result != null && result) {
// In pocket, drop event.
+ mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
return;
}
if (isDoubleTap || isTap) {
@@ -302,6 +307,10 @@
}
gentleWakeUp(pulseReason);
} else if (isPickup) {
+ if (shouldDropPickupEvent()) {
+ mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded");
+ return;
+ }
gentleWakeUp(pulseReason);
} else if (isUdfpsLongPress) {
final State state = mMachine.getState();
@@ -320,7 +329,7 @@
}, true /* alreadyPerformedProxCheck */, pulseReason);
}
- if (isPickup) {
+ if (isPickup && !shouldDropPickupEvent()) {
final long timeSinceNotification =
SystemClock.elapsedRealtime() - mNotificationPulseTime;
final boolean withinVibrationThreshold =
@@ -329,6 +338,10 @@
}
}
+ private boolean shouldDropPickupEvent() {
+ return mKeyguardStateController.isOccluded();
+ }
+
private void gentleWakeUp(int reason) {
// Log screen wake up reason (lift/pickup, tap, double-tap)
Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 2d215e0..a424674 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -100,8 +100,7 @@
*/
public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
boolean updateImmediately) {
- if (type == INDICATION_TYPE_NOW_PLAYING
- || type == INDICATION_TYPE_REVERSE_CHARGING) {
+ if (type == INDICATION_TYPE_REVERSE_CHARGING) {
// temporarily don't show here, instead use AmbientContainer b/181049781
return;
}
@@ -303,7 +302,6 @@
public static final int INDICATION_TYPE_TRUST = 6;
public static final int INDICATION_TYPE_RESTING = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
- public static final int INDICATION_TYPE_NOW_PLAYING = 9;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
@IntDef({
@@ -317,7 +315,6 @@
INDICATION_TYPE_TRUST,
INDICATION_TYPE_RESTING,
INDICATION_TYPE_USER_LOCKED,
- INDICATION_TYPE_NOW_PLAYING,
INDICATION_TYPE_REVERSE_CHARGING,
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 43315f6..8576a28 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -712,6 +712,7 @@
mHandler.removeCallbacks(mAutoDim);
mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
mHandler.removeCallbacks(mEnableLayoutTransitions);
+ mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener);
mFrame = null;
mNavigationBarView = null;
mOrientationHandle = null;
@@ -1391,10 +1392,11 @@
void updateAccessibilityServicesState() {
int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
- boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
- boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
- mNavigationBarView.setAccessibilityButtonState(clickable, longClickable);
-
+ if (mNavigationBarView != null) {
+ boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+ mNavigationBarView.setAccessibilityButtonState(clickable, longClickable);
+ }
updateSystemUiStateFlags(a11yFlags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 2de4026..c02cc8d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -102,10 +102,7 @@
public class NavigationBarView extends FrameLayout implements
NavigationModeController.ModeChangedListener {
final static boolean DEBUG = false;
- final static String TAG = "StatusBar/NavBarView";
-
- // slippery nav bar when everything is disabled, e.g. during setup
- final static boolean SLIPPERY_WHEN_DISABLED = true;
+ final static String TAG = "NavBarView";
final static boolean ALTERNATE_CAR_MODE_UI = false;
private final RegionSamplingHelper mRegionSamplingHelper;
@@ -385,6 +382,12 @@
}
}
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ Log.e(TAG, "onSetAlpha", new Throwable());
+ return super.onSetAlpha(alpha);
+ }
+
public void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
}
@@ -1319,6 +1322,7 @@
dumpButton(pw, "back", getBackButton());
dumpButton(pw, "home", getHomeButton());
+ dumpButton(pw, "handle", getHomeHandle());
dumpButton(pw, "rcnt", getRecentsButton());
dumpButton(pw, "rota", getRotateSuggestionButton());
dumpButton(pw, "a11y", getAccessibilityButton());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index ecc3245..71d2a73 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -96,14 +96,17 @@
mOverviewProxyService.removeCallback(this);
mNavigationModeController.removeListener(this);
mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
+ mEdgeBackGestureHandler.onNavBarDetached();
}
public void init(int displayId) {
mDisplayId = displayId;
mCommandQueue.addCallback(this);
mOverviewProxyService.addCallback(this);
- mNavigationModeController.addListener(this);
+ mEdgeBackGestureHandler.onNavigationModeChanged(
+ mNavigationModeController.addListener(this));
mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener);
+ mEdgeBackGestureHandler.onNavBarAttached();
// Set initial state for any listeners
updateSysuiFlags();
}
@@ -179,15 +182,6 @@
}
@Override
- public void onTaskbarStatusUpdated(boolean visible, boolean stashed) {
- if (visible) {
- mEdgeBackGestureHandler.onNavBarAttached();
- } else {
- mEdgeBackGestureHandler.onNavBarDetached();
- }
- }
-
- @Override
public void onNavigationModeChanged(int mode) {
mEdgeBackGestureHandler.onNavigationModeChanged(mode);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 8659b8b..bfb63ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -64,7 +64,10 @@
// Fade out faster than fade in to finish before QQS hides.
private static final long QQS_FADE_OUT_DURATION = 50L;
-
+ /**
+ * List of all views that will be reset when clearing animation state
+ * see {@link #clearAnimationState()} }
+ */
private final ArrayList<View> mAllViews = new ArrayList<>();
/**
* List of {@link View}s representing Quick Settings that are being animated from the quick QS
@@ -397,6 +400,7 @@
tileView.setClipChildren(true);
tileView.setClipToPadding(true);
firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1);
+ mAllViews.add(tileView.getSecondaryLabel());
}
mAllViews.add(tileView);
@@ -405,21 +409,8 @@
}
if (mAllowFancy) {
- // Make brightness appear static position and alpha in through second half.
- View brightness = mQsPanelController.getBrightnessView();
- if (brightness != null) {
- firstPageBuilder.addFloat(brightness, "translationY",
- brightness.getMeasuredHeight() * 0.5f, 0);
- mBrightnessAnimator = new TouchAnimator.Builder()
- .addFloat(brightness, "alpha", 0, 1)
- .addFloat(brightness, "sliderScaleY", 0.3f, 1)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setStartDelay(0.3f)
- .build();
- mAllViews.add(brightness);
- } else {
- mBrightnessAnimator = null;
- }
+ animateBrightnessSlider(firstPageBuilder);
+
mFirstPageAnimator = firstPageBuilder
.setListener(this)
.build();
@@ -470,20 +461,53 @@
.addFloat(tileLayout, "alpha", 0, 1).build();
}
+ private void animateBrightnessSlider(Builder firstPageBuilder) {
+ View qsBrightness = mQsPanelController.getBrightnessView();
+ View qqsBrightness = mQuickQSPanelController.getBrightnessView();
+ if (qqsBrightness != null && qqsBrightness.getVisibility() == View.VISIBLE) {
+ // animating in split shade mode
+ mAnimatedQsViews.add(qsBrightness);
+ mAllViews.add(qqsBrightness);
+ int translationY = getRelativeTranslationY(qsBrightness, qqsBrightness);
+ mBrightnessAnimator = new Builder()
+ // we need to animate qs brightness even if animation will not be visible,
+ // as we might start from sliderScaleY set to 0.3 if device was in collapsed QS
+ // portrait orientation before
+ .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
+ .addFloat(qqsBrightness, "translationY", 0, translationY)
+ .build();
+ } else if (qsBrightness != null) {
+ firstPageBuilder.addFloat(qsBrightness, "translationY",
+ qsBrightness.getMeasuredHeight() * 0.5f, 0);
+ mBrightnessAnimator = new Builder()
+ .addFloat(qsBrightness, "alpha", 0, 1)
+ .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .setStartDelay(0.3f)
+ .build();
+ mAllViews.add(qsBrightness);
+ } else {
+ mBrightnessAnimator = null;
+ }
+ }
+
private void updateQQSFooterAnimation() {
- int[] qsPosition = new int[2];
- int[] qqsPosition = new int[2];
- View commonView = mQs.getView();
- getRelativePositionInt(qsPosition, mQSFooterActions, commonView);
- getRelativePositionInt(qqsPosition, mQQSFooterActions, commonView);
- int translationY = (qsPosition[1] - qqsPosition[1])
- - mQuickStatusBarHeader.getOffsetTranslation();
+ int translationY = getRelativeTranslationY(mQSFooterActions, mQQSFooterActions);
mQQSFooterActionsAnimator = new TouchAnimator.Builder()
.addFloat(mQQSFooterActions, "translationY", 0, translationY)
.build();
mAnimatedQsViews.add(mQSFooterActions);
}
+ private int getRelativeTranslationY(View view1, View view2) {
+ int[] qsPosition = new int[2];
+ int[] qqsPosition = new int[2];
+ View commonView = mQs.getView();
+ getRelativePositionInt(qsPosition, view1, commonView);
+ getRelativePositionInt(qqsPosition, view2, commonView);
+ return (qsPosition[1] - qqsPosition[1]) - mQuickStatusBarHeader.getOffsetTranslation();
+ }
+
private boolean isIconInAnimatedRow(int count) {
if (mPagedLayout == null) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index a128694..31ddd97 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -208,9 +208,17 @@
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();
+ + mHeader.getHeight()
+ + dragHandleHeight;
}
int calculateContainerBottom() {
@@ -226,6 +234,7 @@
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 4242e1b..6fd8141 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -37,7 +37,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dump.DumpManager;
@@ -238,6 +237,11 @@
mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f);
mQSAnimator.requestAnimatorUpdate();
});
+
+ mQsDragHandler.setOnClickListener(v -> {
+ Log.d(TAG, "drag handler clicked");
+ mCommandQueue.animateExpandSettingsPanel(null);
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 9025427..70892a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -258,10 +258,6 @@
return mView.isLayoutRtl();
}
- public View getBrightnessView() {
- return mView.getBrightnessView();
- }
-
/** */
public void setPageListener(PagedTileLayout.PageListener listener) {
mView.setPageListener(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 42323e3..97568f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -24,6 +24,7 @@
import android.content.ComponentName;
import android.content.res.Configuration;
import android.metrics.LogMaker;
+import android.view.View;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
@@ -405,6 +406,10 @@
mUsingHorizontalLayoutChangedListener = listener;
}
+ public View getBrightnessView() {
+ return mView.getBrightnessView();
+ }
+
/** */
public static final class TileRecord extends QSPanel.Record {
public QSTile tile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 359f3a4..a81d3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -280,6 +280,25 @@
updateBatteryMode();
updateHeadersPadding();
updateAnimators();
+
+ updateClockDatePadding();
+ }
+
+ private void updateClockDatePadding() {
+ int startPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding);
+ int endPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding);
+ mClockView.setPaddingRelative(
+ startPadding,
+ mClockView.getPaddingTop(),
+ endPadding,
+ mClockView.getPaddingBottom()
+ );
+
+ MarginLayoutParams lp = (MarginLayoutParams) mClockDateView.getLayoutParams();
+ lp.setMarginStart(endPadding);
+ mClockDateView.setLayoutParams(lp);
}
private void updateAnimators() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0bcec07..17bcfe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -800,7 +800,9 @@
* Show message on the keyguard for how the user can unlock/enter their device.
*/
public void showActionToUnlock() {
- if (mDozing) {
+ if (mDozing
+ && !mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+ KeyguardUpdateMonitor.getCurrentUser())) {
return;
}
@@ -811,7 +813,7 @@
String message = mContext.getString(R.string.keyguard_retry);
mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
}
- } else if (mKeyguardUpdateMonitor.isScreenOn()) {
+ } else {
showTransientIndication(mContext.getString(R.string.keyguard_unlock),
false /* isError */, true /* hideOnScreenOff */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 2a8771e..7aa2dc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -19,7 +19,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.app.WallpaperManager
import android.os.SystemClock
import android.os.Trace
import android.util.IndentingPrintWriter
@@ -42,6 +41,7 @@
import com.android.systemui.statusbar.phone.PanelExpansionListener
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.WallpaperController
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
@@ -58,7 +58,7 @@
private val biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
private val choreographer: Choreographer,
- private val wallpaperManager: WallpaperManager,
+ private val wallpaperController: WallpaperController,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val dozeParameters: DozeParameters,
dumpManager: DumpManager
@@ -215,15 +215,7 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur)
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur, opaque)
lastAppliedBlur = blur
- try {
- if (root.isAttachedToWindow && root.windowToken != null) {
- wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
- } else {
- Log.i(TAG, "Won't set zoom. Window not attached $root")
- }
- } catch (e: IllegalArgumentException) {
- Log.w(TAG, "Can't set zoom. Window is gone: ${root.windowToken}", e)
- }
+ wallpaperController.setNotificationShadeZoom(zoomOut)
listeners.forEach {
it.onWallpaperZoomOutChanged(zoomOut)
it.onBlurRadiusChanged(blur)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index b053b5d..d7f3225 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -45,6 +45,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
import java.lang.RuntimeException
@@ -67,6 +68,7 @@
private val contentResolver: ContentResolver,
private val configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
+ private val deviceProvisionedController: DeviceProvisionedController,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Main private val handler: Handler,
@@ -95,6 +97,55 @@
}
}
+ private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
+ val filteredTargets = targets.filter(::filterSmartspaceTarget)
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
+
+ private val userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val settingsObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ execution.assertIsMainThread()
+ updateTextColorFromWallpaper()
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ execution.assertIsMainThread()
+ smartspaceViews.forEach { it.setDozeAmount(eased) }
+ }
+ }
+
+ private val deviceProvisionedListener =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onDeviceProvisionedChanged() {
+ connectSession()
+ }
+
+ override fun onUserSetupChanged() {
+ connectSession()
+ }
+ }
+
+ init {
+ deviceProvisionedController.addCallback(deviceProvisionedListener)
+ }
+
fun isEnabled(): Boolean {
execution.assertIsMainThread()
@@ -145,10 +196,20 @@
if (plugin == null || session != null) {
return
}
- val session = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, "lockscreen").build())
- session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ // Only connect after the device is fully provisioned to avoid connection caching
+ // issues
+ if (!deviceProvisionedController.isDeviceProvisioned() ||
+ !deviceProvisionedController.isCurrentUserSetup()) {
+ return
+ }
+
+ val newSession = smartspaceManager.createSmartspaceSession(
+ SmartspaceConfig.Builder(context, "lockscreen").build())
+ newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ this.session = newSession
+
+ deviceProvisionedController.removeCallback(deviceProvisionedListener)
userTracker.addCallback(userTrackerCallback, uiExecutor)
contentResolver.registerContentObserver(
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -159,8 +220,6 @@
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
- this.session = session
-
reloadSmartspace()
}
@@ -197,43 +256,6 @@
plugin?.unregisterListener(listener)
}
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
- val filteredTargets = targets.filter(::filterSmartspaceTarget)
- plugin?.onTargetsAvailable(filteredTargets)
- }
-
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
-
- override fun onProfilesChanged(profiles: List<UserInfo>) {
- }
- }
-
- private val settingsObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
- }
-
- private val configChangeListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- execution.assertIsMainThread()
- updateTextColorFromWallpaper()
- }
- }
-
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- execution.assertIsMainThread()
- smartspaceViews.forEach { it.setDozeAmount(eased) }
- }
- }
-
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
return when (t.userHandle) {
userTracker.userHandle -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 73c4b05..1530e523 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -874,7 +874,7 @@
mRow.getImageResolver().purgeCache();
}
- private class RtlEnabledContext extends ContextWrapper {
+ private static class RtlEnabledContext extends ContextWrapper {
private RtlEnabledContext(Context packageContext) {
super(packageContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6e201048..2c76cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -116,7 +116,7 @@
/**
* Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
- * device while being requested when keyguard is occluded.
+ * device while being requested when keyguard is occluded or showing.
*/
public static final int MODE_UNLOCK_COLLAPSING = 5;
@@ -425,6 +425,11 @@
if (!wasDeviceInteractive) {
mPendingShowBouncer = true;
} else {
+ mShadeController.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE,
+ true /* force */,
+ false /* delayed */,
+ BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
mKeyguardViewController.notifyKeyguardAuthenticated(
false /* strongAuth */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index d348954..e368aad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -57,6 +57,21 @@
private int mUserSwitchPreferredY;
/**
+ * Whether or not there is a custom clock face on keyguard.
+ */
+ private boolean mHasCustomClock;
+
+ /**
+ * Whether or not the NSSL contains any visible notifications.
+ */
+ private boolean mHasVisibleNotifs;
+
+ /**
+ * Height of notification stack: Sum of height of each notification.
+ */
+ private int mNotificationStackHeight;
+
+ /**
* Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
* avatar.
*/
@@ -113,6 +128,25 @@
private boolean mIsSplitShade;
/**
+ * Top location of the udfps icon. This includes the worst case (highest) burn-in
+ * offset that would make the top physically highest on the screen.
+ *
+ * Set to -1 if udfps is not enrolled on the device.
+ */
+ private float mUdfpsTop;
+
+ /**
+ * Bottom y-position of the currently visible clock
+ */
+ private float mClockBottom;
+
+ /**
+ * If true, try to keep clock aligned to the top of the display. Else, assume the clock
+ * is center aligned.
+ */
+ private boolean mIsClockTopAligned;
+
+ /**
* Refreshes the dimension values.
*/
public void loadDimens(Resources res) {
@@ -120,7 +154,7 @@
R.dimen.keyguard_status_view_bottom_margin);
mContainerTopPadding =
- res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2;
+ res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
@@ -134,7 +168,7 @@
int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
float dark, float overStretchAmount, boolean bypassEnabled,
int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset,
- boolean isSplitShade) {
+ boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned) {
mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
userSwitchHeight);
mPanelExpansion = panelExpansion;
@@ -148,6 +182,9 @@
mQsExpansion = qsExpansion;
mCutoutTopInset = cutoutTopInset;
mIsSplitShade = isSplitShade;
+ mUdfpsTop = udfpsTop;
+ mClockBottom = clockBottom;
+ mIsClockTopAligned = isClockTopAligned;
}
public void run(Result result) {
@@ -202,8 +239,34 @@
if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) {
shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock);
}
- float clockYDark = clockY + burnInPreventionOffsetY() + shift;
+ int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset
+ final boolean hasUdfps = mUdfpsTop > -1;
+ if (hasUdfps && !mIsClockTopAligned) {
+ // ensure clock doesn't overlap with the udfps icon
+ if (mUdfpsTop < mClockBottom) {
+ // sometimes the clock textView extends beyond udfps, so let's just use the
+ // space above the KeyguardStatusView/clock as our burn-in offset
+ burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
+ if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+ }
+ shift = -burnInPreventionOffsetY;
+ } else {
+ float upperSpace = clockY - mCutoutTopInset;
+ float lowerSpace = mUdfpsTop - mClockBottom;
+ // center the burn-in offset within the upper + lower space
+ burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
+ if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+ }
+ shift = (lowerSpace - upperSpace) / 2;
+ }
+ }
+
+ float clockYDark = clockY
+ + burnInPreventionOffsetY(burnInPreventionOffsetY)
+ + shift;
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
@@ -235,9 +298,7 @@
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
- private float burnInPreventionOffsetY() {
- int offset = mBurnInPreventionOffsetYClock;
-
+ private float burnInPreventionOffsetY(int offset) {
return getBurnInOffset(offset * 2, false /* xAxis */) - offset;
}
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 198ad98..b21088a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -57,6 +57,9 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorLocationInternal;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
@@ -568,6 +571,8 @@
*/
private float mKeyguardOnlyContentAlpha = 1.0f;
+ private float mUdfpsMaxYBurnInOffset;
+
/**
* Are we currently in gesture navigation
*/
@@ -798,13 +803,13 @@
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
- UserAvatarView userAvatarView = null;
+ FrameLayout userAvatarContainer = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
if (mKeyguardQsUserSwitchEnabled) {
ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
- userAvatarView = (UserAvatarView) stub.inflate();
+ userAvatarContainer = (FrameLayout) stub.inflate();
} else {
ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
@@ -820,7 +825,7 @@
updateViewControllers(
mView.findViewById(R.id.keyguard_status_view),
- userAvatarView,
+ userAvatarContainer,
keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
@@ -907,10 +912,11 @@
mView.getContext());
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
+ mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
- UserAvatarView userAvatarView,
+ FrameLayout userAvatarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
@@ -1066,7 +1072,7 @@
!mKeyguardQsUserSwitchEnabled
&& mKeyguardUserSwitcherEnabled
&& isUserSwitcherEnabled;
- UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+ FrameLayout userAvatarView = (FrameLayout) reInflateStub(
R.id.keyguard_qs_user_switch_view /* viewId */,
R.id.keyguard_qs_user_switch_stub /* stubId */,
R.layout.keyguard_qs_user_switch /* layoutId */,
@@ -1265,7 +1271,17 @@
float darkamount =
mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
? 1.0f : mInterpolatedDarkAmount;
- mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard,
+
+ float udfpsAodTopLocation = -1f;
+ if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) {
+ FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
+ final SensorLocationInternal location = props.getLocation();
+ udfpsAodTopLocation = location.sensorLocationY - location.sensorRadius
+ - mUdfpsMaxYBurnInOffset;
+ }
+
+ mClockPositionAlgorithm.setup(
+ mStatusBarHeaderHeightKeyguard,
expandedFraction,
mKeyguardStatusViewController.getLockscreenHeight(),
userIconHeight,
@@ -1274,7 +1290,10 @@
bypassEnabled, getUnlockedStackScrollerPadding(),
computeQsExpansionFraction(),
mDisplayTopInset,
- mShouldUseSplitNotificationShade);
+ mShouldUseSplitNotificationShade,
+ udfpsAodTopLocation,
+ mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
+ mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = animate || mAnimateNextPositionUpdate;
@@ -3546,6 +3565,7 @@
}
public void dozeTimeTick() {
+ mLockIconViewController.dozeTimeTick();
mKeyguardBottomArea.dozeTimeTick();
mKeyguardStatusViewController.dozeTimeTick();
if (mInterpolatedDarkAmount > 0) {
@@ -3760,7 +3780,7 @@
return new TouchHandler() {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
+ if (mBlockTouches || mQs.disallowPanelTouches()) {
return false;
}
initDownStates(event);
@@ -3851,10 +3871,6 @@
mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
}
- if (mLockIconViewController.onTouchEvent(event)) {
- return true;
- }
-
handled |= super.onTouch(v, event);
return !mDozing || mPulsing || handled;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 147ebfb..03d0bb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dock.DockManager;
@@ -87,6 +88,7 @@
private final NotificationShadeDepthController mDepthController;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final LockIconViewController mLockIconViewController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private GestureDetector mGestureDetector;
@@ -137,7 +139,8 @@
NotificationPanelViewController notificationPanelViewController,
StatusBarWindowView statusBarWindowView,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ LockIconViewController lockIconViewController) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -162,6 +165,7 @@
mStatusBarWindowView = statusBarWindowView;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLockIconViewController = lockIconViewController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -234,6 +238,7 @@
if (!isCancel && mService.shouldIgnoreTouch()) {
return false;
}
+
if (isDown) {
setTouchActive(true);
mTouchCancelled = false;
@@ -244,6 +249,7 @@
if (mTouchCancelled || mExpandAnimationRunning) {
return false;
}
+
mFalsingCollector.onTouchEvent(ev);
mGestureDetector.onTouchEvent(ev);
mStatusBarKeyguardViewManager.onTouch(ev);
@@ -259,9 +265,17 @@
if (isDown) {
mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev);
}
+
if (mStatusBarStateController.isDozing()) {
mService.mDozeScrimController.extendPulse();
}
+ mLockIconViewController.onTouchEvent(
+ ev,
+ () -> mService.wakeUpIfDozing(
+ SystemClock.uptimeMillis(),
+ mView,
+ "LOCK_ICON_TOUCH"));
+
// In case we start outside of the view bounds (below the status bar), we need to
// dispatch
// the touch manually as the view system can't accommodate for touches outside of
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6b96f3c..ee7725f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -228,7 +228,9 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;
import com.android.systemui.volume.VolumeComponent;
@@ -537,7 +539,9 @@
private final FeatureFlags mFeatureFlags;
private final UnfoldTransitionConfig mUnfoldTransitionConfig;
private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation;
+ private final Lazy<UnfoldTransitionWallpaperController> mUnfoldWallpaperController;
private final Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimation;
+ private final WallpaperController mWallpaperController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final MessageRouter mMessageRouter;
private final WallpaperManager mWallpaperManager;
@@ -771,7 +775,9 @@
BrightnessSlider.Factory brightnessSliderFactory,
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+ Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController,
Lazy<StatusBarMoveFromCenterAnimationController> statusBarUnfoldAnimationController,
+ WallpaperController wallpaperController,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
@@ -865,6 +871,8 @@
mBrightnessSliderFactory = brightnessSliderFactory;
mUnfoldTransitionConfig = unfoldTransitionConfig;
mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation;
+ mUnfoldWallpaperController = unfoldTransitionWallpaperController;
+ mWallpaperController = wallpaperController;
mMoveFromCenterAnimation = statusBarUnfoldAnimationController;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
@@ -1052,6 +1060,7 @@
if (mUnfoldTransitionConfig.isEnabled()) {
mUnfoldLightRevealOverlayAnimation.get().init();
+ mUnfoldWallpaperController.get().init();
}
mPluginManager.addPluginListener(
@@ -1117,6 +1126,7 @@
inflateStatusBarWindow();
mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
+ mWallpaperController.setRootView(mNotificationShadeWindowView);
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
@@ -4294,6 +4304,8 @@
return;
}
WallpaperInfo info = mWallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
+ mWallpaperController.onWallpaperInfoUpdated(info);
+
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
// If WallpaperInfo is null, it must be ImageWallpaper.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index f8120a8..143aaba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -8,13 +8,13 @@
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
-import com.android.systemui.statusbar.StatusBarState
import android.view.View
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.notification.AnimatableProperty
import com.android.systemui.statusbar.notification.PropertyAnimator
@@ -61,6 +61,7 @@
private var shouldAnimateInKeyguard = false
private var lightRevealAnimationPlaying = false
private var aodUiAnimationPlaying = false
+ private var callbacks = HashSet<Callback>()
/**
* The result of our decision whether to play the screen off animation in
@@ -72,11 +73,17 @@
private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
duration = LIGHT_REVEAL_ANIMATION_DURATION
interpolator = Interpolators.LINEAR
- addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float }
+ addUpdateListener {
+ lightRevealScrim.revealAmount = it.animatedValue as Float
+ sendUnlockedScreenOffProgressUpdate(
+ 1f - (it.animatedFraction as Float),
+ 1f - (it.animatedValue as Float))
+ }
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator?) {
lightRevealScrim.revealAmount = 1f
lightRevealAnimationPlaying = false
+ sendUnlockedScreenOffProgressUpdate(0f, 0f)
}
override fun onAnimationEnd(animation: Animator?) {
@@ -243,7 +250,21 @@
return true
}
- /**
+ fun addCallback(callback: Callback) {
+ callbacks.add(callback)
+ }
+
+ fun removeCallback(callback: Callback) {
+ callbacks.remove(callback)
+ }
+
+ fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) {
+ callbacks.forEach {
+ it.onUnlockedScreenOffProgressUpdate(linear, eased)
+ }
+ }
+
+/**
* Whether we're doing the light reveal animation or we're done with that and animating in the
* AOD UI.
*/
@@ -262,4 +283,8 @@
fun isScreenOffLightRevealAnimationPlaying(): Boolean {
return lightRevealAnimationPlaying
}
+
+ interface Callback {
+ fun onUnlockedScreenOffProgressUpdate(linear: Float, eased: Float)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index c45068e0..13eb75a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -107,7 +107,9 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;
import com.android.systemui.volume.VolumeComponent;
@@ -216,7 +218,9 @@
BrightnessSlider.Factory brightnessSliderFactory,
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+ Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController,
Lazy<StatusBarMoveFromCenterAnimationController> statusBarMoveFromCenterAnimation,
+ WallpaperController wallpaperController,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
@@ -312,7 +316,9 @@
brightnessSliderFactory,
unfoldTransitionConfig,
unfoldLightRevealOverlayAnimation,
+ unfoldTransitionWallpaperController,
statusBarMoveFromCenterAnimation,
+ wallpaperController,
ongoingCallController,
animationScheduler,
locationPublisher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 5e70d0d..d838a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
@@ -56,7 +57,7 @@
* Manages the user switch on the Keyguard that is used for opening the QS user panel.
*/
@KeyguardUserSwitcherScope
-public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {
private static final String TAG = "KeyguardQsUserSwitchController";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -76,6 +77,7 @@
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUserDetailAdapter mUserDetailAdapter;
private NotificationPanelViewController mNotificationPanelViewController;
+ private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
// State info for the user switch and keyguard
@@ -111,7 +113,7 @@
@Inject
public KeyguardQsUserSwitchController(
- UserAvatarView view,
+ FrameLayout view,
Context context,
@Main Resources resources,
ScreenLifecycle screenLifecycle,
@@ -143,6 +145,7 @@
protected void onInit() {
super.onInit();
if (DEBUG) Log.d(TAG, "onInit");
+ mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
@@ -150,11 +153,10 @@
}
};
- mView.setOnClickListener(v -> {
+ mUserAvatarView.setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
-
if (isListAnimating()) {
return;
}
@@ -163,7 +165,7 @@
openQsUserPanel();
});
- mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
@@ -237,12 +239,12 @@
R.string.accessibility_multi_user_switch_switcher);
}
- if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
- mView.setContentDescription(contentDescription);
+ if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) {
+ mUserAvatarView.setContentDescription(contentDescription);
}
int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
- mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
+ mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
}
Drawable getCurrentUserIcon() {
@@ -269,7 +271,7 @@
* Get the height of the keyguard user switcher view when closed.
*/
public int getUserIconHeight() {
- return mView.getHeight();
+ return mUserAvatarView.getHeight();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index fe0b970..e2d0bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -178,8 +178,7 @@
Map<String, OverlayIdentifier> categoryToPackage,
FabricatedOverlay[] pendingCreation,
int currentUser,
- Set<UserHandle> managedProfiles,
- Runnable onOverlaysApplied) {
+ Set<UserHandle> managedProfiles) {
mBgExecutor.execute(() -> {
// Disable all overlays that have not been specified in the user setting.
@@ -226,7 +225,6 @@
try {
mOverlayManager.commit(transaction.build());
- mMainExecutor.execute(onOverlaysApplied);
} catch (SecurityException | IllegalStateException e) {
Log.e(TAG, "setEnabled failed", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index a288d999..cd5865f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -549,21 +549,17 @@
.map(key -> key + " -> " + categoryToPackage.get(key)).collect(
Collectors.joining(", ")));
}
- Runnable overlaysAppliedRunnable = this::onOverlaysApplied;
if (mNeedsOverlayCreation) {
mNeedsOverlayCreation = false;
mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
mSecondaryOverlay, mNeutralOverlay
- }, currentUser, managedProfiles, overlaysAppliedRunnable);
+ }, currentUser, managedProfiles);
} else {
mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
- managedProfiles, overlaysAppliedRunnable);
+ managedProfiles);
}
}
- protected void onOverlaysApplied() {
- }
-
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("mSystemColors=" + mCurrentColors);
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
new file mode 100644
index 0000000..8dd3d6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
@@ -0,0 +1,39 @@
+/*
+ * 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
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.WallpaperController
+import javax.inject.Inject
+
+@SysUISingleton
+class UnfoldTransitionWallpaperController @Inject constructor(
+ private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val wallpaperController: WallpaperController
+) {
+
+ fun init() {
+ unfoldTransitionProgressProvider.addCallback(TransitionListener())
+ }
+
+ private inner class TransitionListener : TransitionProgressListener {
+ override fun onTransitionProgress(progress: Float) {
+ wallpaperController.setUnfoldTransitionZoom(progress)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
new file mode 100644
index 0000000..db2aca8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.util.Log
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlin.math.max
+
+private const val TAG = "WallpaperController"
+
+@SysUISingleton
+class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) {
+
+ var rootView: View? = null
+
+ private var notificationShadeZoomOut: Float = 0f
+ private var unfoldTransitionZoomOut: Float = 0f
+
+ private var wallpaperInfo: WallpaperInfo? = null
+
+ fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) {
+ this.wallpaperInfo = wallpaperInfo
+ }
+
+ private val shouldUseDefaultUnfoldTransition: Boolean
+ get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
+ ?: true
+
+ fun setNotificationShadeZoom(zoomOut: Float) {
+ notificationShadeZoomOut = zoomOut
+ updateZoom()
+ }
+
+ fun setUnfoldTransitionZoom(zoomOut: Float) {
+ if (shouldUseDefaultUnfoldTransition) {
+ unfoldTransitionZoomOut = zoomOut
+ updateZoom()
+ }
+ }
+
+ private fun updateZoom() {
+ setWallpaperZoom(max(notificationShadeZoomOut, unfoldTransitionZoomOut))
+ }
+
+ private fun setWallpaperZoom(zoomOut: Float) {
+ try {
+ rootView?.let { root ->
+ if (root.isAttachedToWindow && root.windowToken != null) {
+ wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
+ } else {
+ Log.i(TAG, "Won't set zoom. Window not attached $root")
+ }
+ }
+ } catch (e: IllegalArgumentException) {
+ Log.w(TAG, "Can't set zoom. Window is gone: ${rootView?.windowToken}", e)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 2b4b49b..d44fb76 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -220,6 +220,12 @@
}
@Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.wallet_activity_options_menu, menu);
return super.onCreateOptionsMenu(menu);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 8d615f0..88b596c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -119,6 +119,7 @@
when(mNotificationIcons.getLayoutParams()).thenReturn(
mock(RelativeLayout.LayoutParams.class));
when(mView.getContext()).thenReturn(getContext());
+ when(mView.getResources()).thenReturn(mResources);
when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
@@ -127,7 +128,6 @@
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mView.isAttachedToWindow()).thenReturn(true);
- when(mResources.getString(anyInt())).thenReturn("h:mm");
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController = new KeyguardClockSwitchController(
mView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
index f09d7b7..7e9f84c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -19,8 +19,9 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.View.OVER_SCROLL_ALWAYS;
import static android.view.View.OVER_SCROLL_NEVER;
+import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.systemBars;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +40,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.testing.AndroidTestingRunner;
@@ -98,14 +100,15 @@
private AccessibilityFloatingMenuView mMenuView;
private RecyclerView mListView = new RecyclerView(mContext);
- private int mScreenHeight;
private int mMenuWindowHeight;
private int mMenuHalfWidth;
private int mMenuHalfHeight;
- private int mScreenHalfWidth;
- private int mScreenHalfHeight;
+ private int mDisplayHalfWidth;
+ private int mDisplayHalfHeight;
private int mMaxWindowX;
private int mMaxWindowY;
+ private final int mDisplayWindowWidth = 1080;
+ private final int mDisplayWindowHeight = 2340;
@Before
public void initMenuView() {
@@ -113,7 +116,10 @@
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
-
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, mDisplayWindowWidth,
+ mDisplayWindowHeight));
+ when(mWindowMetrics.getWindowInsets()).thenReturn(fakeDisplayInsets());
mMenuView = spy(
new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, mListView));
}
@@ -129,18 +135,16 @@
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height);
final int menuWidth = padding * 2 + iconWidthHeight;
final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding;
- final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
- mScreenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
mMenuHalfWidth = menuWidth / 2;
mMenuHalfHeight = menuHeight / 2;
- mScreenHalfWidth = screenWidth / 2;
- mScreenHalfHeight = mScreenHeight / 2;
+ mDisplayHalfWidth = mDisplayWindowWidth / 2;
+ mDisplayHalfHeight = mDisplayWindowHeight / 2;
int marginStartEnd =
mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT
? margin : 0;
- mMaxWindowX = screenWidth - marginStartEnd - menuWidth;
+ mMaxWindowX = mDisplayWindowWidth - marginStartEnd - menuWidth;
mMenuWindowHeight = menuHeight + margin * 2;
- mMaxWindowY = mScreenHeight - mMenuWindowHeight;
+ mMaxWindowY = mDisplayWindowHeight - mMenuWindowHeight;
}
@Test
@@ -279,15 +283,15 @@
final MotionEvent moveEvent =
mMotionEventHelper.obtainMotionEvent(2, 3,
MotionEvent.ACTION_MOVE,
- /* screenCenterX */mScreenHalfWidth
- - /* offsetXToScreenLeftHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */mDisplayHalfWidth
+ - /* offsetXToDisplayLeftHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
final MotionEvent upEvent =
mMotionEventHelper.obtainMotionEvent(4, 5,
MotionEvent.ACTION_UP,
- /* screenCenterX */ mScreenHalfWidth
- - /* offsetXToScreenLeftHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */ mDisplayHalfWidth
+ - /* offsetXToDisplayLeftHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
listView.dispatchTouchEvent(downEvent);
listView.dispatchTouchEvent(moveEvent);
@@ -315,15 +319,15 @@
final MotionEvent moveEvent =
mMotionEventHelper.obtainMotionEvent(2, 3,
MotionEvent.ACTION_MOVE,
- /* screenCenterX */mScreenHalfWidth
- + /* offsetXToScreenRightHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */mDisplayHalfWidth
+ + /* offsetXToDisplayRightHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
final MotionEvent upEvent =
mMotionEventHelper.obtainMotionEvent(4, 5,
MotionEvent.ACTION_UP,
- /* screenCenterX */ mScreenHalfWidth
- + /* offsetXToScreenRightHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */ mDisplayHalfWidth
+ + /* offsetXToDisplayRightHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
listView.dispatchTouchEvent(downEvent);
listView.dispatchTouchEvent(moveEvent);
@@ -332,12 +336,12 @@
assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
- /* newWindowY = screenCenterY - offsetY */ mScreenHalfHeight - mMenuHalfHeight);
+ /* newWindowY = displayCenterY - offsetY */ mDisplayHalfHeight - mMenuHalfHeight);
}
@Test
- public void tapOnAndDragMenuToScreenSide_transformShapeHalfOval() {
+ public void tapOnAndDragMenuToDisplaySide_transformShapeHalfOval() {
final Position alignRightPosition = new Position(1.0f, 0.8f);
final RecyclerView listView = new RecyclerView(mContext);
final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
@@ -355,13 +359,13 @@
mMotionEventHelper.obtainMotionEvent(2, 3,
MotionEvent.ACTION_MOVE,
/* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToScreenRightSide */ mMenuHalfWidth,
+ + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
/* downY */ (currentWindowY + mMenuHalfHeight));
final MotionEvent upEvent =
mMotionEventHelper.obtainMotionEvent(4, 5,
MotionEvent.ACTION_UP,
/* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToScreenRightSide */ mMenuHalfWidth,
+ + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
/* downY */ (currentWindowY + mMenuHalfHeight));
listView.dispatchTouchEvent(downEvent);
@@ -423,7 +427,7 @@
}
@Test
- public void showMenuAndIme_withHigherIme_alignScreenTopEdge() {
+ public void showMenuAndIme_withHigherIme_alignDisplayTopEdge() {
final int offset = 99999;
setupBasicMenuView(mMenuView);
@@ -475,10 +479,21 @@
private WindowInsets fakeImeInsetWith(AccessibilityFloatingMenuView menuView, int offset) {
// Ensure the keyboard has overlapped on the menu view.
final int fakeImeHeight =
- mScreenHeight - (menuView.mCurrentLayoutParams.y + mMenuWindowHeight) + offset;
+ mDisplayWindowHeight - (menuView.mCurrentLayoutParams.y + mMenuWindowHeight)
+ + offset;
return new WindowInsets.Builder()
- .setVisible(ime() | navigationBars(), true)
- .setInsets(ime() | navigationBars(), Insets.of(0, 0, 0, fakeImeHeight))
+ .setVisible(ime(), true)
+ .setInsets(ime(), Insets.of(0, 0, 0, fakeImeHeight))
+ .build();
+ }
+
+ private WindowInsets fakeDisplayInsets() {
+ final int fakeStatusBarHeight = 75;
+ final int fakeNavigationBarHeight = 125;
+ return new WindowInsets.Builder()
+ .setVisible(systemBars() | displayCutout(), true)
+ .setInsets(systemBars() | displayCutout(),
+ Insets.of(0, fakeStatusBarHeight, 0, fakeNavigationBarHeight))
.build();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
index eb1f15b..3553a0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
@@ -23,12 +23,16 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.filters.SmallTest;
@@ -52,6 +56,9 @@
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+
private AccessibilityFloatingMenuView mMenuView;
private BaseTooltipView mToolTipView;
@@ -66,6 +73,9 @@
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect());
+ when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
mMenuView = new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition);
mToolTipView = new BaseTooltipView(mContext, mMenuView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
index ca4e3e9..9eba49d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
@@ -21,12 +21,16 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.test.filters.SmallTest;
@@ -49,6 +53,9 @@
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+
private AccessibilityFloatingMenuView mMenuView;
private DockTooltipView mDockTooltipView;
private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
@@ -62,6 +69,9 @@
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect());
+ when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
mMenuView = spy(new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition));
mDockTooltipView = new DockTooltipView(mContext, mMenuView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
index dae4364..ea104a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
@@ -22,12 +22,15 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -57,6 +60,8 @@
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
private RecyclerView mListView;
private AccessibilityFloatingMenuView mMenuView;
private ItemDelegateCompat mItemDelegateCompat;
@@ -69,6 +74,9 @@
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect());
+ when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
mListView = new RecyclerView(mContext);
mMenuView =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index e3566e7..deabda3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -58,7 +58,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -66,6 +65,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.Execution;
@@ -124,8 +124,6 @@
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
@Mock
private FalsingManager mFalsingManager;
@@ -153,6 +151,8 @@
private ConfigurationController mConfigurationController;
@Mock
private SystemClock mSystemClock;
+ @Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private FakeExecutor mFgExecutor;
@@ -192,6 +192,7 @@
when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null))
.thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD
when(mEnrollView.getContext()).thenReturn(mContext);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -222,7 +223,6 @@
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
- mKeyguardViewMediator,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
@@ -236,7 +236,8 @@
mDisplayManager,
mHandler,
mConfigurationController,
- mSystemClock);
+ mSystemClock,
+ mUnlockedScreenOffAnimationController);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -478,11 +479,12 @@
@Test
public void aodInterrupt() throws RemoteException {
- // GIVEN that the overlay is showing and screen is on
+ // GIVEN that the overlay is showing and screen is on and fp is running
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
// THEN illumination begins
@@ -500,6 +502,7 @@
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
// WHEN it is cancelled
@@ -515,6 +518,7 @@
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
// WHEN it times out
@@ -533,6 +537,24 @@
mFgExecutor.runAllReady();
// WHEN aod interrupt is received
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+
+ // THEN no illumination because screen is off
+ verify(mUdfpsView, never()).startIllumination(any());
+ }
+
+ @Test
+ public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+ // GIVEN showing overlay
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+ mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+
+ // WHEN aod interrupt is received when the fingerprint service isn't running
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// THEN no illumination because screen is off
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index be3e535..2821f3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -89,6 +90,8 @@
@Mock
private ConfigurationController mConfigurationController;
@Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ @Mock
private UdfpsController mUdfpsController;
private FakeSystemClock mSystemClock = new FakeSystemClock();
@@ -121,13 +124,12 @@
Optional.of(mStatusBar),
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
- mExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
mSystemClock,
mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
mUdfpsController);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 10997fa..9577c7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -83,6 +84,8 @@
private AuthController mAuthController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
@@ -114,7 +117,7 @@
mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
- mAuthController, mExecutor, mUiEventLogger);
+ mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
@@ -217,6 +220,32 @@
}
@Test
+ public void testPickupGesture() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard isn't occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN wakeup
+ verify(mMachine).wakeUp();
+ }
+
+ @Test
+ public void testPickupGestureDroppedKeyguardOccluded() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard IS occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN never wakeup
+ verify(mMachine, never()).wakeUp();
+ }
+
+ @Test
public void testOnSensor_Fingerprint() {
// GIVEN dozing state
when(mMachine.getState()).thenReturn(DOZE_AOD);
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 3e9fbcf..af6dee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -18,6 +18,7 @@
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -50,6 +51,8 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.airbnb.lottie.LottieAnimationView;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,6 +84,7 @@
private @Mock DelayableExecutor mDelayableExecutor;
private @Mock Vibrator mVibrator;
private @Mock AuthRippleController mAuthRippleController;
+ private @Mock LottieAnimationView mAodFp;
private LockIconViewController mLockIconViewController;
@@ -102,6 +106,7 @@
when(mLockIconView.getContext()).thenReturn(mContext);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+ when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
mLockIconViewController = new LockIconViewController(
mLockIconView,
@@ -122,7 +127,7 @@
@Test
public void testUpdateFingerprintLocationOnInit() {
- // GIVEN fp sensor location is available pre-init
+ // GIVEN fp sensor location is available pre-attached
final PointF udfpsLocation = new PointF(50, 75);
final int radius = 33;
final FingerprintSensorPropertiesInternal fpProps =
@@ -141,7 +146,7 @@
// WHEN lock icon view controller is initialized and attached
mLockIconViewController.init();
captureAttachListener();
- mAttachListener.onViewAttachedToWindow(null);
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
// THEN lock icon view location is updated with the same coordinates as fpProps
verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
@@ -154,8 +159,10 @@
when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
when(mAuthController.getUdfpsProps()).thenReturn(null);
mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
- // GIVEN fp sensor location is available post-init
+ // GIVEN fp sensor location is available post-atttached
captureAuthControllerCallback();
final PointF udfpsLocation = new PointF(50, 75);
final int radius = 33;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index ed3c473..36228dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -56,6 +56,7 @@
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
@@ -122,6 +123,8 @@
EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
@Mock
EdgeBackGestureHandler mEdgeBackGestureHandler;
+ @Mock
+ NavigationBarA11yHelper mNavigationBarA11yHelper;
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
@@ -256,6 +259,20 @@
assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
}
+ @Test
+ public void testA11yEventAfterDetach() {
+ View v = mNavigationBar.createView(null);
+ mNavigationBar.onViewAttachedToWindow(v);
+ verify(mNavigationBarA11yHelper).registerA11yEventListener(any(
+ NavigationBarA11yHelper.NavA11yEventListener.class));
+ mNavigationBar.onViewDetachedFromWindow(v);
+ verify(mNavigationBarA11yHelper).removeA11yEventListener(any(
+ NavigationBarA11yHelper.NavA11yEventListener.class));
+
+ // Should be safe even though the internal view is now null.
+ mNavigationBar.updateAccessibilityServicesState();
+ }
+
private NavigationBar createNavBar(Context context) {
DeviceProvisionedController deviceProvisionedController =
mock(DeviceProvisionedController.class);
@@ -287,7 +304,7 @@
mHandler,
mock(NavigationBarOverlayController.class),
mUiEventLogger,
- mock(NavigationBarA11yHelper.class),
+ mNavigationBarA11yHelper,
mock(UserTracker.class)));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 465370b..e5ae65f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar
-import android.app.WallpaperManager
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -32,6 +31,7 @@
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -47,10 +47,8 @@
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doThrow
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import java.util.function.Consumer
@@ -65,7 +63,7 @@
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var choreographer: Choreographer
- @Mock private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var wallpaperController: WallpaperController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var root: View
@@ -101,7 +99,7 @@
notificationShadeDepthController = NotificationShadeDepthController(
statusBarStateController, blurUtils, biometricUnlockController,
- keyguardStateController, choreographer, wallpaperManager,
+ keyguardStateController, choreographer, wallpaperController,
notificationShadeWindowController, dozeParameters, dumpManager)
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
@@ -209,7 +207,7 @@
notificationShadeDepthController.qsPanelExpansion = 0.25f
notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager).setWallpaperZoomOut(any(),
+ verify(wallpaperController).setNotificationShadeZoom(
eq(Interpolators.getNotificationScrimAlpha(0.25f, false /* notifications */)))
}
@@ -242,14 +240,14 @@
notificationShadeDepthController.transitionToFullShadeProgress = 1f
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(0), eq(false))
- verify(wallpaperManager).setWallpaperZoomOut(any(), eq(1f))
+ verify(wallpaperController).setNotificationShadeZoom(eq(1f))
}
@Test
fun updateBlurCallback_setsBlurAndZoom() {
notificationShadeDepthController.addListener(listener)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
+ verify(wallpaperController).setNotificationShadeZoom(anyFloat())
verify(listener).onWallpaperZoomOutChanged(anyFloat())
verify(blurUtils).applyBlur(any(), anyInt(), eq(false))
}
@@ -279,21 +277,6 @@
}
@Test
- fun updateBlurCallback_invalidWindow() {
- `when`(root.isAttachedToWindow).thenReturn(false)
- notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager, times(0)).setWallpaperZoomOut(any(), anyFloat())
- }
-
- @Test
- fun updateBlurCallback_exception() {
- doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
- .setWallpaperZoomOut(any(), anyFloat())
- notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
- }
-
- @Test
fun ignoreShadeBlurUntilHidden_schedulesFrame() {
notificationShadeDepthController.blursDisabledForAppLaunch = true
verify(choreographer).postFrameCallback(
@@ -322,7 +305,7 @@
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
- verify(wallpaperManager).setWallpaperZoomOut(any(), eq(1f))
+ verify(wallpaperController).setNotificationShadeZoom(eq(1f))
verify(blurUtils).applyBlur(eq(viewRootImpl), eq(0), eq(false))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 575e01cf..c8fec02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
import com.android.systemui.util.concurrency.FakeExecution
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -89,6 +91,8 @@
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock
private lateinit var handler: Handler
@Mock
@@ -106,12 +110,15 @@
private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
@Captor
private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+ @Captor
+ private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
private lateinit var sessionListener: OnTargetsAvailableListener
private lateinit var userListener: UserTracker.Callback
private lateinit var settingsObserver: ContentObserver
private lateinit var configChangeListener: ConfigurationListener
private lateinit var statusBarStateListener: StateListener
+ private lateinit var deviceProvisionedListener: DeviceProvisionedListener
private lateinit var smartspaceView: SmartspaceView
@@ -145,6 +152,8 @@
`when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
`when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
setActiveUser(userHandlePrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
@@ -162,11 +171,15 @@
contentResolver,
configurationController,
statusBarStateController,
+ deviceProvisionedController,
execution,
executor,
handler,
Optional.of(plugin)
)
+
+ verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
+ deviceProvisionedListener = deviceProvisionedCaptor.value
}
@Test(expected = RuntimeException::class)
@@ -181,6 +194,27 @@
}
@Test
+ fun connectOnlyAfterDeviceIsProvisioned() {
+ // GIVEN an unprovisioned device and an attempt to connect
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+
+ // WHEN a connection attempt is made
+ controller.buildAndConnectView(fakeParent)
+
+ // THEN no session is created
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+ // WHEN it does become provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onUserSetupChanged()
+
+ // THEN the session is created
+ verify(smartspaceManager).createSmartspaceSession(any())
+ }
+
+ @Test
fun testListenersAreRegistered() {
// GIVEN a listener is added after a session is created
connectSession()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 60f0b68..4276f7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -274,6 +274,26 @@
}
@Test
+ public void onBiometricAuthenticated_onLockScreen() {
+ // GIVEN not dozing
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+
+ // WHEN we want to unlock collapse
+ mBiometricUnlockController.startWakeAndUnlock(
+ BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
+
+ // THEN we collpase the panels and notify authenticated
+ verify(mShadeController).animateCollapsePanels(
+ /* flags */ anyInt(),
+ /* force */ eq(true),
+ /* delayed */ eq(false),
+ /* speedUpFactor */ anyFloat()
+ );
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(
+ /* strongAuth */ eq(false));
+ }
+
+ @Test
public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
reset(mUpdateMonitor);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index f34f21b..d098e1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -16,42 +16,80 @@
package com.android.systemui.statusbar.phone;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.AdditionalAnswers.returnsFirstArg;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.util.BurnInHelperKt;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private static final int SCREEN_HEIGHT = 2000;
- private static final int EMPTY_MARGIN = 0;
private static final int EMPTY_HEIGHT = 0;
private static final float ZERO_DRAG = 0.f;
private static final float OPAQUE = 1.f;
private static final float TRANSPARENT = 0.f;
+
+ @Mock
+ private Resources mResources;
+
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
+
+ private MockitoSession mStaticMockSession;
+ private int mNotificationStackHeight;
+
private float mPanelExpansion;
+ private int mKeyguardStatusBarHeaderHeight;
private int mKeyguardStatusHeight;
private float mDark;
private float mQsExpansion;
- private int mCutoutTopInsetPx = 0;
+ private int mCutoutTopInset = 0;
private boolean mIsSplitShade = false;
+ private float mUdfpsTop = -1;
+ private float mClockBottom = SCREEN_HEIGHT / 2;
+ private boolean mClockTopAligned;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession = mockitoSession()
+ .mockStatic(BurnInHelperKt.class)
+ .startMocking();
+
mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+ when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
+ mClockPositionAlgorithm.loadDimens(mResources);
+
mClockPosition = new KeyguardClockPositionAlgorithm.Result();
}
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
@Test
public void clockPositionTopOfScreenOnAOD() {
// GIVEN on AOD and clock has 0 height
@@ -71,7 +109,7 @@
// GIVEN on AOD and clock has 0 height
givenAOD();
mKeyguardStatusHeight = EMPTY_HEIGHT;
- mCutoutTopInsetPx = 300;
+ mCutoutTopInset = 300;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position is below the cutout
@@ -271,6 +309,155 @@
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
+ @Test
+ public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN the clock + udfps are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the worst-case clock Y position is shifted only by 100 (not the full 200),
+ // so that it's at the same location as mUdfpsTop
+ assertThat(mClockPosition.clockY).isEqualTo(100);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at 0
+ assertThat(mClockPosition.clockY).isEqualTo(0);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD)
+ mKeyguardStatusBarHeaderHeight = 150;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in since the burn in offset > space above clock
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset (0 in this case)
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is only 25px (less than space above clock)
+ givenAOD();
+ int maxYBurnInOffset = 25;
+ givenMaxBurnInOffset(maxYBurnInOffset);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts above mKeyguardStatusBarHeaderHeight
+ assertThat(mClockPosition.clockY).isEqualTo(
+ mKeyguardStatusBarHeaderHeight - 2 * maxYBurnInOffset);
+ }
+
+ @Test
+ public void clockPositionShiftsToMaximizeUdfpsBurnInMovement() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+ int upperSpaceAvailable = mKeyguardStatusBarHeaderHeight - mCutoutTopInset;
+
+ // GIVEN the bottom of the clock and the top of UDFPS are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+ float lowerSpaceAvailable = mUdfpsTop - mClockBottom;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use both the area above
+ // the clock and below the clock (vertically centered in its allowed area)
+ assertThat(mClockPosition.clockY).isEqualTo(
+ (int) (mCutoutTopInset + upperSpaceAvailable + lowerSpaceAvailable));
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ private void givenHighestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg());
+ }
+
+ private void givenLowestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(0);
+ }
+
+ private void givenMaxBurnInOffset(int offset) {
+ when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_clock))
+ .thenReturn(offset);
+ mClockPositionAlgorithm.loadDimens(mResources);
+ }
+
private void givenAOD() {
mPanelExpansion = 1.f;
mDark = 1.f;
@@ -281,12 +468,28 @@
mDark = 0.f;
}
+ /**
+ * Setup and run the clock position algorithm.
+ *
+ * mClockPosition.clockY will contain the top y-coordinate for the clock position
+ */
private void positionClock() {
- mClockPositionAlgorithm.setup(EMPTY_MARGIN, mPanelExpansion, mKeyguardStatusHeight,
- 0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */,
- mDark, ZERO_DRAG, false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */, mQsExpansion,
- mCutoutTopInsetPx, mIsSplitShade);
+ mClockPositionAlgorithm.setup(
+ mKeyguardStatusBarHeaderHeight,
+ mPanelExpansion,
+ mKeyguardStatusHeight,
+ 0 /* userSwitchHeight */,
+ 0 /* userSwitchPreferredY */,
+ mDark,
+ ZERO_DRAG,
+ false /* bypassEnabled */,
+ 0 /* unlockedStackScrollerPadding */,
+ mQsExpansion,
+ mCutoutTopInset,
+ mIsSplitShade,
+ mUdfpsTop,
+ mClockBottom,
+ mClockTopAligned);
mClockPositionAlgorithm.run(mClockPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index 3d887dd..c62d73c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -32,6 +32,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
@@ -96,6 +97,7 @@
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ @Mock private LockIconViewController mLockIconViewController;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -141,7 +143,8 @@
mNotificationPanelViewController,
mStatusBarWindowView,
mNotificationStackScrollLayoutController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mLockIconViewController);
mController.setupExpandedStatusBar();
mController.setService(mStatusBar, mNotificationShadeWindowController);
mController.setDragDownHelper(mDragDownHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 751bc81..88a3827 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -140,7 +140,9 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.MessageRouterImpl;
import com.android.systemui.util.time.FakeSystemClock;
@@ -256,6 +258,8 @@
@Mock private UnfoldTransitionConfig mUnfoldTransitionConfig;
@Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy;
@Mock private Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimationLazy;
+ @Mock private Lazy<UnfoldTransitionWallpaperController> mUnfoldWallpaperController;
+ @Mock private WallpaperController mWallpaperController;
@Mock private OngoingCallController mOngoingCallController;
@Mock private SystemStatusAnimationScheduler mAnimationScheduler;
@Mock private StatusBarLocationPublisher mLocationPublisher;
@@ -431,7 +435,9 @@
mBrightnessSliderFactory,
mUnfoldTransitionConfig,
mUnfoldLightRevealOverlayAnimationLazy,
+ mUnfoldWallpaperController,
mMoveFromCenterAnimationLazy,
+ mWallpaperController,
mOngoingCallController,
mAnimationScheduler,
mLocationPublisher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index e6dc4db..2c461ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -96,8 +96,6 @@
DumpManager mDumpManager;
@Mock
OverlayManagerTransaction.Builder mTransactionBuilder;
- @Mock
- Runnable mOnOverlaysApplied;
private ThemeOverlayApplier mManager;
private boolean mGetOverlayInfoEnabled = true;
@@ -176,7 +174,7 @@
@Test
public void allCategoriesSpecified_allEnabledExclusively() {
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES, mOnOverlaysApplied);
+ TEST_USER_HANDLES);
verify(mOverlayManager).commit(any());
for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
@@ -188,7 +186,7 @@
@Test
public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES, mOnOverlaysApplied);
+ TEST_USER_HANDLES);
for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
@@ -205,9 +203,8 @@
public void allCategoriesSpecified_enabledForAllUserHandles() {
Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- userHandles, mOnOverlaysApplied);
+ userHandles);
- verify(mOnOverlaysApplied).run();
for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
eq(TEST_USER.getIdentifier()));
@@ -223,7 +220,7 @@
Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- userHandles, mOnOverlaysApplied);
+ userHandles);
for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true),
@@ -237,7 +234,7 @@
mock(FabricatedOverlay.class)
};
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation,
- TEST_USER.getIdentifier(), TEST_USER_HANDLES, mOnOverlaysApplied);
+ TEST_USER.getIdentifier(), TEST_USER_HANDLES);
for (FabricatedOverlay overlay : pendingCreation) {
verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
@@ -251,7 +248,7 @@
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES, mOnOverlaysApplied);
+ TEST_USER_HANDLES);
for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
@@ -268,7 +265,7 @@
@Test
public void zeroCategoriesSpecified_allDisabled() {
mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES, mOnOverlaysApplied);
+ TEST_USER_HANDLES);
for (String category : THEME_CATEGORIES) {
verify(mTransactionBuilder).setEnabled(
@@ -283,7 +280,7 @@
categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES, mOnOverlaysApplied);
+ TEST_USER_HANDLES);
verify(mTransactionBuilder, never()).setEnabled(
eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 4c282bf..f89bbe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -161,7 +161,7 @@
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -183,7 +183,7 @@
mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
null, null), WallpaperManager.FLAG_SYSTEM);
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -204,7 +204,7 @@
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -240,7 +240,7 @@
.isFalse();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -270,7 +270,7 @@
"android.theme.customization.color_both\":\"0")).isTrue();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -300,7 +300,7 @@
"android.theme.customization.color_both\":\"1")).isTrue();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -327,7 +327,7 @@
assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
.isFalse();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -354,7 +354,7 @@
assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
.isFalse();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -382,7 +382,7 @@
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -411,14 +411,14 @@
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
public void onProfileAdded_setsTheme() {
mBroadcastReceiver.getValue().onReceive(null,
new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -428,7 +428,7 @@
mBroadcastReceiver.getValue().onReceive(null,
new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -438,7 +438,7 @@
mBroadcastReceiver.getValue().onReceive(null,
new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -450,7 +450,7 @@
Color.valueOf(Color.BLUE), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
// Regression test: null events should not reset the internal state and allow colors to be
// applied again.
@@ -458,11 +458,11 @@
mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM);
verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
- any(), any());
+ any());
mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.GREEN),
null, null), WallpaperManager.FLAG_SYSTEM);
verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
- any(), any());
+ any());
}
@Test
@@ -499,7 +499,7 @@
verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
// Colors were applied during controller initialization.
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
clearInvocations(mThemeOverlayApplier);
}
@@ -533,7 +533,7 @@
verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
// Colors were applied during controller initialization.
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
clearInvocations(mThemeOverlayApplier);
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -542,12 +542,12 @@
// Defers event because we already have initial colors.
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
// Then event happens after setup phase is over.
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
mDeviceProvisionedListener.getValue().onUserSetupChanged();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -568,11 +568,11 @@
Color.valueOf(Color.RED), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -592,10 +592,10 @@
Color.valueOf(Color.RED), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
}
@Test
@@ -614,7 +614,7 @@
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
new file mode 100644
index 0000000..3cb19e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.view.ViewRootImpl
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class WallpaperControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var wallpaperManager: WallpaperManager
+ @Mock
+ private lateinit var root: View
+ @Mock
+ private lateinit var viewRootImpl: ViewRootImpl
+ @Mock
+ private lateinit var windowToken: IBinder
+
+ @JvmField
+ @Rule
+ val mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var wallaperController: WallpaperController
+
+ @Before
+ fun setup() {
+ `when`(root.viewRootImpl).thenReturn(viewRootImpl)
+ `when`(root.windowToken).thenReturn(windowToken)
+ `when`(root.isAttachedToWindow).thenReturn(true)
+
+ wallaperController = WallpaperController(wallpaperManager)
+
+ wallaperController.rootView = root
+ }
+
+ @Test
+ fun setNotificationShadeZoom_updatesWallpaperManagerZoom() {
+ wallaperController.setNotificationShadeZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.5f))
+ }
+
+ @Test
+ fun setUnfoldTransitionZoom_updatesWallpaperManagerZoom() {
+ wallaperController.setUnfoldTransitionZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.5f))
+ }
+
+ @Test
+ fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
+ wallaperController.onWallpaperInfoUpdated(createWallpaperInfo(
+ useDefaultUnfoldTransition = false
+ ))
+
+ wallaperController.setUnfoldTransitionZoom(0.5f)
+
+ verify(wallpaperManager, never()).setWallpaperZoomOut(any(), anyFloat())
+ }
+
+ @Test
+ fun setUnfoldTransitionZoomAndNotificationShadeZoom_updatesWithMaximumZoom() {
+ wallaperController.setUnfoldTransitionZoom(0.7f)
+ clearInvocations(wallpaperManager)
+
+ wallaperController.setNotificationShadeZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.7f))
+ }
+
+ @Test
+ fun setNotificationShadeZoomAndThenUnfoldTransition_updatesWithMaximumZoom() {
+ wallaperController.setNotificationShadeZoom(0.7f)
+ clearInvocations(wallpaperManager)
+
+ wallaperController.setUnfoldTransitionZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.7f))
+ }
+
+ @Test
+ fun setNotificationZoom_invalidWindow_doesNotSetZoom() {
+ `when`(root.isAttachedToWindow).thenReturn(false)
+
+ verify(wallpaperManager, times(0)).setWallpaperZoomOut(any(), anyFloat())
+ }
+
+ @Test
+ fun setNotificationZoom_exceptionWhenUpdatingZoom_doesNotFail() {
+ doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
+ .setWallpaperZoomOut(any(), anyFloat())
+
+ wallaperController.setNotificationShadeZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
+ }
+
+ private fun createWallpaperInfo(useDefaultUnfoldTransition: Boolean = true): WallpaperInfo {
+ val info = mock(WallpaperInfo::class.java)
+ whenever(info.shouldUseDefaultUnfoldTransition()).thenReturn(useDefaultUnfoldTransition)
+ return info
+ }
+}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index 9f3045e..3a90a95 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -87,7 +87,7 @@
mId = sessionId;
mUid = uid;
mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null,
- activityId, appComponentName, displayId, flags);
+ activityId, appComponentName, displayId, activityToken, flags);
mSessionStateReceiver = sessionStateReceiver;
try {
sessionStateReceiver.asBinder().linkToDeath(() -> onClientDeath(), 0);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index cbc3238..e5716ee 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -107,7 +107,6 @@
":display-device-config",
":display-layout-config",
":device-state-config",
- ":guiconstants_aidl",
"java/com/android/server/EventLogTags.logtags",
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/wm/EventLogTags.logtags",
@@ -150,7 +149,7 @@
"android.hardware.biometrics.fingerprint-V2.3-java",
"android.hardware.biometrics.fingerprint-V1-java",
"android.hardware.oemlock-V1.0-java",
- "android.hardware.configstore-V1.0-java",
+ "android.hardware.configstore-V1.1-java",
"android.hardware.contexthub-V1.0-java",
"android.hardware.rebootescrow-V1-java",
"android.hardware.soundtrigger-V2.3-java",
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 36c0de9..ad0485b 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1033,6 +1033,7 @@
mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
state.setProcStateChanged(false);
}
+ trimMemoryUiHiddenIfNecessaryLSP(app);
if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) {
try {
@@ -1075,24 +1076,6 @@
}
profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
} else {
- if ((curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
- // If this application is now in the background and it
- // had done UI, then give it the special trim level to
- // have it free UI resources.
- final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
- if (trimMemoryLevel < level && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
- + app.processName + " to " + level);
- }
- thread.scheduleTrimMemory(level);
- } catch (RemoteException e) {
- }
- }
- profile.setPendingUiClean(false);
- }
if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {
try {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
@@ -1119,28 +1102,36 @@
mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
state.setProcStateChanged(false);
}
- if ((state.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
- if (profile.getTrimMemoryLevel() < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
- && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ,
- "Trimming memory of ui hidden " + app.processName
- + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- }
- thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- } catch (RemoteException e) {
- }
- }
- profile.setPendingUiClean(false);
- }
+ trimMemoryUiHiddenIfNecessaryLSP(app);
profile.setTrimMemoryLevel(0);
});
}
return allChanged;
}
+ @GuardedBy({"mService", "mProcLock"})
+ private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) {
+ if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
+ // If this application is now in the background and it
+ // had done UI, then give it the special trim level to
+ // have it free UI resources.
+ final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
+ IApplicationThread thread;
+ if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
+ try {
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
+ Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
+ + app.processName + " to " + level);
+ }
+ thread.scheduleTrimMemory(level);
+ } catch (RemoteException e) {
+ }
+ }
+ app.mProfile.setPendingUiClean(false);
+ }
+ }
+
@GuardedBy("mProcLock")
long getLowRamTimeSinceIdleLPr(long now) {
return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5d0bad2..8c19284 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -243,7 +243,7 @@
*/
private static final int FLAG_ADJUST_VOLUME = 1;
- private final Context mContext;
+ final Context mContext;
private final ContentResolver mContentResolver;
private final AppOpsManager mAppOps;
@@ -318,6 +318,8 @@
private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
private static final int MSG_DISPATCH_AUDIO_MODE = 40;
private static final int MSG_ROUTING_UPDATED = 41;
+ private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
+ private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1037,12 +1039,13 @@
mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
+ mHasSpatializerEffect = SystemProperties.getBoolean("ro.audio.spatializer_enabled", false);
+
// done with service initialization, continue additional work in our Handler thread
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
- 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
-
+ 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
}
/**
@@ -1236,6 +1239,9 @@
// routing monitoring from AudioSystemAdapter
@Override
public void onRoutingUpdatedFromNative() {
+ if (!mHasSpatializerEffect) {
+ return;
+ }
sendMsg(mAudioHandler,
MSG_ROUTING_UPDATED,
SENDMSG_REPLACE, 0, 0, null,
@@ -1432,8 +1438,10 @@
}
}
- // TODO check property if feature enabled
- mSpatializerHelper.reset(/* featureEnabled */ SPATIALIZER_FEATURE_ENABLED_DEFAULT);
+ if (mHasSpatializerEffect) {
+ mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled());
+ monitorRoutingChanges(true);
+ }
onIndicateSystemReady();
// indicate the end of reconfiguration phase to audio HAL
@@ -7569,12 +7577,18 @@
break;
case MSG_INIT_SPATIALIZER:
- mSpatializerHelper.init();
- // TODO read property to see if enabled
- mSpatializerHelper.setFeatureEnabled(SPATIALIZER_FEATURE_ENABLED_DEFAULT);
+ mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
+ if (mHasSpatializerEffect) {
+ mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled());
+ monitorRoutingChanges(true);
+ }
mAudioEventWakeLock.release();
break;
+ case MSG_INIT_HEADTRACKING_SENSORS:
+ mSpatializerHelper.onInitSensors(/*init*/ msg.arg1 == 1);
+ break;
+
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
@@ -7711,6 +7725,10 @@
case MSG_ROUTING_UPDATED:
mSpatializerHelper.onRoutingUpdated();
break;
+
+ case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
+ onPersistSpatialAudioEnabled(msg.arg1 == 1);
+ break;
}
}
}
@@ -8280,7 +8298,40 @@
//==========================================================================================
private final @NonNull SpatializerHelper mSpatializerHelper;
- private static final boolean SPATIALIZER_FEATURE_ENABLED_DEFAULT = false;
+ /**
+ * Initialized from property ro.audio.spatializer_enabled
+ * Should only be 1 when the device ships with a Spatializer effect
+ */
+ private final boolean mHasSpatializerEffect;
+ /**
+ * Default value for the spatial audio feature
+ */
+ private static final boolean SPATIAL_AUDIO_ENABLED_DEFAULT = true;
+
+ /**
+ * persist in user settings whether the feature is enabled.
+ * Can change when {@link Spatializer#setEnabled(boolean)} is called and successfully
+ * changes the state of the feature
+ * @param featureEnabled
+ */
+ void persistSpatialAudioEnabled(boolean featureEnabled) {
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_SPATIAL_AUDIO_ENABLED,
+ SENDMSG_REPLACE, featureEnabled ? 1 : 0, 0, null,
+ /*delay ms*/ 100);
+ }
+
+ void onPersistSpatialAudioEnabled(boolean enabled) {
+ Settings.Secure.putIntForUser(mContentResolver,
+ Settings.Secure.SPATIAL_AUDIO_ENABLED, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ boolean isSpatialAudioEnabled() {
+ return Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.SPATIAL_AUDIO_ENABLED, SPATIAL_AUDIO_ENABLED_DEFAULT ? 1 : 0,
+ UserHandle.USER_CURRENT) == 1;
+ }
private void enforceModifyDefaultAudioEffectsPermission() {
if (mContext.checkCallingOrSelfPermission(
@@ -8433,6 +8484,32 @@
mSpatializerHelper.setDesiredHeadTrackingMode(mode);
}
+ /** @see Spatializer#setEffectParameter */
+ public void setSpatializerParameter(int key, @NonNull byte[] value) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(value);
+ mSpatializerHelper.setEffectParameter(key, value);
+ }
+
+ /** @see Spatializer#getEffectParameter */
+ public void getSpatializerParameter(int key, @NonNull byte[] value) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(value);
+ mSpatializerHelper.getEffectParameter(key, value);
+ }
+
+ /**
+ * post a message to schedule init/release of head tracking sensors
+ * @param init initialization if true, release if false
+ */
+ void postInitSpatializerHeadTrackingSensors(boolean init) {
+ sendMsg(mAudioHandler,
+ MSG_INIT_HEADTRACKING_SENSORS,
+ SENDMSG_REPLACE,
+ /*arg1*/ init ? 1 : 0,
+ 0, TAG, /*delay*/ 0);
+ }
+
//==========================================================================================
private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) ||
@@ -9013,6 +9090,12 @@
sVolumeLogger.dump(pw);
pw.println("\n");
dumpSupportedSystemUsage(pw);
+
+ pw.println("\n");
+ pw.println("\nSpatial audio:");
+ pw.println("mHasSpatializerEffect:" + mHasSpatializerEffect);
+ pw.println("isSpatializerEnabled:" + isSpatializerEnabled());
+ pw.println("isSpatialAudioEnabled:" + isSpatialAudioEnabled());
}
private void dumpSupportedSystemUsage(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 1d85974..b2fa86b 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioFormat;
@@ -26,7 +29,9 @@
import android.media.ISpatializer;
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerHeadTrackingCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.SpatializationLevel;
import android.media.Spatializer;
import android.media.SpatializerHeadTrackingMode;
import android.os.RemoteCallbackList;
@@ -44,6 +49,7 @@
private static final String TAG = "AS.SpatializerHelper";
private static final boolean DEBUG = true;
+ private static final boolean DEBUG_MORE = false;
private static void logd(String s) {
if (DEBUG) {
@@ -53,6 +59,7 @@
private final @NonNull AudioSystemAdapter mASA;
private final @NonNull AudioService mAudioService;
+ private @Nullable SensorManager mSensorManager;
//------------------------------------------------------------
// Spatializer state machine
@@ -71,6 +78,8 @@
private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
private @Nullable ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback;
+ private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
+
// default attributes and format that determine basic availability of spatialization
private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder()
@@ -97,8 +106,13 @@
mASA = asa;
}
- synchronized void init() {
+ synchronized void init(boolean effectExpected) {
Log.i(TAG, "Initializing");
+ if (!effectExpected) {
+ Log.i(TAG, "Setting state to STATE_NOT_SUPPORTED due to effect not expected");
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
if (mState != STATE_UNINITIALIZED) {
throw new IllegalStateException(("init() called in state:" + mState));
}
@@ -124,7 +138,7 @@
for (byte level : levels) {
logd("found support for level: " + level);
if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) {
- logd("Setting Spatializer to LEVEL_MULTICHANNEL");
+ logd("Setting capable level to LEVEL_MULTICHANNEL");
mCapableSpatLevel = level;
break;
}
@@ -156,7 +170,7 @@
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
- init();
+ init(true);
setFeatureEnabled(featureEnabled);
}
@@ -188,13 +202,24 @@
public void onLevelChanged(byte level) {
logd("SpatializerCallback.onLevelChanged level:" + level);
synchronized (SpatializerHelper.this) {
- mSpatLevel = level;
+ mSpatLevel = spatializationLevelToSpatializerInt(level);
}
// TODO use reported spat level to change state
- }
+ // init sensors
+ if (level == SpatializationLevel.NONE) {
+ initSensors(/*init*/false);
+ } else {
+ postInitSensors(true);
+ }
+ }
+ };
+
+ // spatializer head tracking callback from native
+ private final class SpatializerHeadTrackingCallback
+ extends ISpatializerHeadTrackingCallback.Stub {
public void onHeadTrackingModeChanged(byte mode) {
- logd("SpatializerCallback.onHeadTrackingModeChanged mode:" + mode);
+ logd("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" + mode);
int oldMode, newMode;
synchronized (this) {
oldMode = mActualHeadTrackingMode;
@@ -208,21 +233,22 @@
public void onHeadToSoundStagePoseUpdated(float[] headToStage) {
if (headToStage == null) {
- Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform");
+ Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated"
+ + "null transform");
return;
}
if (headToStage.length != 6) {
- Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated invalid transform length"
- + headToStage.length);
+ Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated"
+ + " invalid transform length" + headToStage.length);
return;
}
- if (DEBUG) {
+ if (DEBUG_MORE) {
// 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters
StringBuilder t = new StringBuilder(42);
for (float val : headToStage) {
t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]");
}
- logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t);
+ logd("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" + t);
}
dispatchPoseUpdate(headToStage);
}
@@ -370,7 +396,7 @@
}
}
mStateCallbacks.finishBroadcast();
- // TODO persist enabled state
+ mAudioService.persistSpatialAudioEnabled(featureEnabled);
}
private synchronized void setDispatchAvailableState(boolean available) {
@@ -433,9 +459,14 @@
private void createSpat() {
if (mSpat == null) {
mSpatCallback = new SpatializerCallback();
+ mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback();
mSpat = AudioSystem.getSpatializer(mSpatCallback);
try {
mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
+ //TODO: register heatracking callback only when sensors are registered
+ if (mSpat.isHeadTrackingSupported()) {
+ mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Can't set spatializer level", e);
mState = STATE_NOT_SUPPORTED;
@@ -451,6 +482,7 @@
if (mSpat != null) {
mSpatCallback = null;
try {
+ mSpat.registerHeadTrackingCallback(null);
mSpat.release();
mSpat = null;
} catch (RemoteException e) {
@@ -627,36 +659,6 @@
}
}
- private int headTrackingModeTypeToSpatializerInt(byte mode) {
- switch (mode) {
- case SpatializerHeadTrackingMode.OTHER:
- return Spatializer.HEAD_TRACKING_MODE_OTHER;
- case SpatializerHeadTrackingMode.DISABLED:
- return Spatializer.HEAD_TRACKING_MODE_DISABLED;
- case SpatializerHeadTrackingMode.RELATIVE_WORLD:
- return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
- case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
- return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
- default:
- throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode));
- }
- }
-
- private byte spatializerIntToHeadTrackingModeType(int sdkMode) {
- switch (sdkMode) {
- case Spatializer.HEAD_TRACKING_MODE_OTHER:
- return SpatializerHeadTrackingMode.OTHER;
- case Spatializer.HEAD_TRACKING_MODE_DISABLED:
- return SpatializerHeadTrackingMode.DISABLED;
- case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
- return SpatializerHeadTrackingMode.RELATIVE_WORLD;
- case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
- return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
- default:
- throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
- }
- }
-
private boolean checkSpatForHeadTracking(String funcName) {
switch (mState) {
case STATE_UNINITIALIZED:
@@ -728,4 +730,151 @@
}
mHeadPoseCallbacks.finishBroadcast();
}
+
+ //------------------------------------------------------
+ // vendor parameters
+ synchronized void setEffectParameter(int key, @NonNull byte[] value) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't set parameter key:" + key + " without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for setParameter for key:" + key));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ mSpat.setParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setParameter for key:" + key, e);
+ }
+ }
+
+ synchronized void getEffectParameter(int key, @NonNull byte[] value) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't get parameter key:" + key + " without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for getParameter for key:" + key));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ mSpat.getParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getParameter for key:" + key, e);
+ }
+ }
+
+ //------------------------------------------------------
+ // sensors
+ private void initSensors(boolean init) {
+ if (mSensorManager == null) {
+ mSensorManager = (SensorManager)
+ mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE);
+ }
+ final int headHandle;
+ final int screenHandle;
+ if (init) {
+ if (mSensorManager == null) {
+ Log.e(TAG, "Null SensorManager, can't init sensors");
+ return;
+ }
+ // TODO replace with dynamic association of sensor for headtracker
+ Sensor headSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);
+ headHandle = headSensor.getHandle();
+ //Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ //screenHandle = deviceSensor.getHandle();
+ screenHandle = -1;
+ } else {
+ // -1 is disable value
+ screenHandle = -1;
+ headHandle = -1;
+ }
+ try {
+ Log.i(TAG, "setScreenSensor:" + screenHandle);
+ mSpat.setScreenSensor(screenHandle);
+ } catch (Exception e) {
+ Log.e(TAG, "Error calling setScreenSensor:" + screenHandle, e);
+ }
+ try {
+ Log.i(TAG, "setHeadSensor:" + headHandle);
+ mSpat.setHeadSensor(headHandle);
+ } catch (Exception e) {
+ Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e);
+ }
+ }
+
+ private void postInitSensors(boolean init) {
+ mAudioService.postInitSpatializerHeadTrackingSensors(init);
+ }
+
+ synchronized void onInitSensors(boolean init) {
+ final int[] modes = getSupportedHeadTrackingModes();
+ if (modes.length == 0) {
+ Log.i(TAG, "not initializing sensors, no headtracking supported");
+ return;
+ }
+ initSensors(init);
+ }
+
+ //------------------------------------------------------
+ // SDK <-> AIDL converters
+ private static int headTrackingModeTypeToSpatializerInt(byte mode) {
+ switch (mode) {
+ case SpatializerHeadTrackingMode.OTHER:
+ return Spatializer.HEAD_TRACKING_MODE_OTHER;
+ case SpatializerHeadTrackingMode.DISABLED:
+ return Spatializer.HEAD_TRACKING_MODE_DISABLED;
+ case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
+ case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
+ default:
+ throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode));
+ }
+ }
+
+ private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
+ switch (sdkMode) {
+ case Spatializer.HEAD_TRACKING_MODE_OTHER:
+ return SpatializerHeadTrackingMode.OTHER;
+ case Spatializer.HEAD_TRACKING_MODE_DISABLED:
+ return SpatializerHeadTrackingMode.DISABLED;
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
+ return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
+ return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+ default:
+ throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
+ }
+ }
+
+ private static int spatializationLevelToSpatializerInt(byte level) {
+ switch (level) {
+ case SpatializationLevel.NONE:
+ return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+ return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
+ case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+ return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
+ default:
+ throw(new IllegalArgumentException("Unexpected spatializer level:" + level));
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 8a11e8d..ca051e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -167,14 +167,17 @@
@Override
protected void stopHalOperation() {
mSensorOverlays.hide(getSensorId());
-
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
+ if (mCancellationSignal != null) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ } else {
+ Slog.e(TAG, "cancellation signal was null");
}
}
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index f6ba369..b5846b5 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -183,14 +183,15 @@
*/
synchronized boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
@Nullable Long versionCode) {
+ if (packageName == null) {
+ return false;
+ }
boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
-
// If the app is not installed or no longer has raw overrides, evaluate to false
if (versionCode == null || !mRawOverrides.containsKey(packageName) || !allowed) {
removePackageOverrideInternal(packageName);
return false;
}
-
// Evaluate the override based on its version
int overrideValue = mRawOverrides.get(packageName).evaluate(versionCode);
switch (overrideValue) {
@@ -266,6 +267,9 @@
* @return {@code true} if the change should be enabled for the package.
*/
boolean willBeEnabled(String packageName) {
+ if (packageName == null) {
+ return defaultValue();
+ }
final PackageOverride override = mRawOverrides.get(packageName);
if (override != null) {
switch (override.evaluateForAllVersions()) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 806a5dd..792feea 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManagerInternal;
import android.hardware.devicestate.IDeviceStateManager;
import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
@@ -161,6 +162,7 @@
@Override
public void onStart() {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
+ publishLocalService(DeviceStateManagerInternal.class, new LocalService());
}
@VisibleForTesting
@@ -240,13 +242,6 @@
}
/** Returns the list of currently supported device state identifiers. */
- private int[] getSupportedStateIdentifiers() {
- synchronized (mLock) {
- return getSupportedStateIdentifiersLocked();
- }
- }
-
- /** Returns the list of currently supported device state identifiers. */
private int[] getSupportedStateIdentifiersLocked() {
int[] supportedStates = new int[mDeviceStates.size()];
for (int i = 0; i < supportedStates.length; i++) {
@@ -848,4 +843,14 @@
}
}
}
+
+ /** Implementation of {@link DeviceStateManagerInternal} published as a local service. */
+ private final class LocalService extends DeviceStateManagerInternal {
+ @Override
+ public int[] getSupportedStateIdentifiers() {
+ synchronized (mLock) {
+ return getSupportedStateIdentifiersLocked();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 827523b..f16ed41 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -53,6 +53,7 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManagerInternal;
import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
@@ -131,6 +132,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
@@ -210,6 +212,7 @@
private WindowManagerInternal mWindowManagerInternal;
private InputManagerInternal mInputManagerInternal;
private IMediaProjectionManager mProjectionService;
+ private DeviceStateManagerInternal mDeviceStateManager;
private int[] mUserDisabledHdrTypes = {};
private boolean mAreUserDisabledHdrTypesAllowed = true;
@@ -557,10 +560,9 @@
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
- DeviceStateManager deviceStateManager =
- mContext.getSystemService(DeviceStateManager.class);
- deviceStateManager.registerCallback(new HandlerExecutor(mHandler),
- new DeviceStateListener());
+ mDeviceStateManager = LocalServices.getService(DeviceStateManagerInternal.class);
+ mContext.getSystemService(DeviceStateManager.class).registerCallback(
+ new HandlerExecutor(mHandler), new DeviceStateListener());
scheduleTraversalLocked(false);
}
@@ -3274,6 +3276,53 @@
}
@Override
+ public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) {
+ synchronized (mSyncRoot) {
+ // Retrieve the group associated with this display id.
+ final int displayGroupId =
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId);
+ if (displayGroupId == Display.INVALID_DISPLAY_GROUP) {
+ Slog.w(TAG,
+ "Can't get possible display info since display group for " + displayId
+ + " does not exist");
+ return new ArraySet<>();
+ }
+
+ // Assume any display in this group can be swapped out for the given display id.
+ Set<DisplayInfo> possibleInfo = new ArraySet<>();
+ final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked(
+ displayGroupId);
+ for (int i = 0; i < group.getSizeLocked(); i++) {
+ final int id = group.getIdLocked(i);
+ final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id);
+ if (logical == null) {
+ Slog.w(TAG,
+ "Can't get possible display info since logical display for "
+ + "display id " + id + " does not exist, as part of group "
+ + displayGroupId);
+ } else {
+ possibleInfo.add(logical.getDisplayInfoLocked());
+ }
+ }
+
+ // For the supported device states, retrieve the DisplayInfos for the logical
+ // display layout.
+ if (mDeviceStateManager == null) {
+ Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready");
+ } else {
+ final int[] supportedStates =
+ mDeviceStateManager.getSupportedStateIdentifiers();
+ for (int state : supportedStates) {
+ possibleInfo.addAll(
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId,
+ displayGroupId));
+ }
+ }
+ return possibleInfo;
+ }
+ }
+
+ @Override
public Point getDisplayPosition(int displayId) {
synchronized (mSyncRoot) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index a931718..973dcc4 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -24,6 +24,7 @@
import android.os.Message;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -38,6 +39,7 @@
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -254,6 +256,61 @@
return mDisplayGroups.get(groupId);
}
+ /**
+ * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is
+ * part of the same display group as the provided display id. The DisplayInfo represent the
+ * logical display layouts possible for the given device state.
+ *
+ * @param deviceState the state to query possible layouts for
+ * @param displayId the display id to apply to all displays within the group
+ * @param groupId the display group to filter display info for. Must be the same group as
+ * the display with the provided display id.
+ */
+ public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId,
+ int groupId) {
+ Set<DisplayInfo> displayInfos = new ArraySet<>();
+ final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
+ final int layoutSize = layout.size();
+ for (int i = 0; i < layoutSize; i++) {
+ Layout.Display displayLayout = layout.getAt(i);
+ if (displayLayout == null) {
+ continue;
+ }
+
+ // If the underlying display-device we want to use for this display
+ // doesn't exist, then skip it. This can happen at startup as display-devices
+ // trickle in one at a time. When the new display finally shows up, the layout is
+ // recalculated so that the display is properly added to the current layout.
+ final DisplayAddress address = displayLayout.getAddress();
+ final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
+ if (device == null) {
+ Slog.w(TAG, "The display device (" + address + "), is not available"
+ + " for the display state " + deviceState);
+ continue;
+ }
+
+ // Find or create the LogicalDisplay to map the DisplayDevice to.
+ final int logicalDisplayId = displayLayout.getLogicalDisplayId();
+ final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
+ if (logicalDisplay == null) {
+ Slog.w(TAG, "The logical display (" + address + "), is not available"
+ + " for the display state " + deviceState);
+ continue;
+ }
+ final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked();
+ DisplayInfo displayInfo = new DisplayInfo(temp);
+ if (displayInfo.displayGroupId != groupId) {
+ // Ignore any displays not in the provided group.
+ continue;
+ }
+ // A display in the same group can be swapped out at any point, so set the display id
+ // for all results to the provided display id.
+ displayInfo.displayId = displayId;
+ displayInfos.add(displayInfo);
+ }
+ return displayInfos;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("LogicalDisplayMapper:");
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 382398a..e0cc8e1 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -352,7 +352,7 @@
}
private void handleSafeModeStatusChanged() {
- logDbg("VcnGatewayConnection safe mode status changed");
+ logVdbg("VcnGatewayConnection safe mode status changed");
boolean hasSafeModeGatewayConnection = false;
// If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
@@ -368,7 +368,7 @@
hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
if (oldStatus != mCurrentStatus) {
mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
- logDbg(
+ logInfo(
"Safe mode "
+ (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
}
@@ -539,6 +539,16 @@
Slog.d(TAG, getLogPrefix() + msg, tr);
}
+ private void logInfo(String msg) {
+ Slog.i(TAG, getLogPrefix() + msg);
+ LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg);
+ }
+
+ private void logInfo(String msg, Throwable tr) {
+ Slog.i(TAG, getLogPrefix() + msg, tr);
+ LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr);
+ }
+
private void logErr(String msg) {
Slog.e(TAG, getLogPrefix() + msg);
LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 450257f..7dec4e7 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1677,10 +1677,8 @@
mFailedAttempts = 0;
cancelSafeModeAlarm();
- if (mIsInSafeMode) {
- mIsInSafeMode = false;
- mGatewayStatusCallback.onSafeModeStatusChanged();
- }
+ mIsInSafeMode = false;
+ mGatewayStatusCallback.onSafeModeStatusChanged();
}
protected void applyTransform(
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 946fa44..c45b661 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -223,7 +223,6 @@
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -1389,6 +1388,20 @@
return parent != null ? parent.asTaskFragment() : null;
}
+ /** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */
+ private boolean shouldStartChangeTransition(
+ @Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) {
+ if (mWmService.mDisableTransitionAnimation
+ || mDisplayContent == null || newParent == null || oldParent == null
+ || getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) {
+ return false;
+ }
+
+ // Transition change for the activity moving into a TaskFragment of different bounds.
+ return newParent.isOrganizedTaskFragment()
+ && !newParent.getBounds().equals(oldParent.getBounds());
+ }
+
@Override
void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) {
final TaskFragment oldParent = (TaskFragment) rawOldParent;
@@ -1397,6 +1410,10 @@
final Task newTask = newParent != null ? newParent.getTask() : null;
this.task = newTask;
+ if (shouldStartChangeTransition(newParent, oldParent)) {
+ initializeChangeTransition(getBounds());
+ }
+
super.onParentChanged(newParent, oldParent);
if (isPersistable()) {
@@ -2607,6 +2624,7 @@
return parent != null ? parent.getOrganizedTaskFragment() : null;
}
+ @Override
boolean isEmbedded() {
final TaskFragment parent = getTaskFragment();
return parent != null && parent.isEmbedded();
@@ -8022,13 +8040,10 @@
@VisibleForTesting
@Override
Rect getAnimationBounds(int appRootTaskClipMode) {
- if (appRootTaskClipMode == ROOT_TASK_CLIP_BEFORE_ANIM && getRootTask() != null) {
- // Using the root task bounds here effectively applies the clipping before animation.
- return getRootTask().getBounds();
- }
- // Use task-bounds if available so that activity-level letterbox (maxAspectRatio) is
+ // Use TaskFragment-bounds if available so that activity-level letterbox (maxAspectRatio) is
// included in the animation.
- return task != null ? task.getBounds() : getBounds();
+ final TaskFragment taskFragment = getTaskFragment();
+ return taskFragment != null ? taskFragment.getBounds() : getBounds();
}
@Override
@@ -9250,14 +9265,16 @@
task.getBounds(), Type.systemBars(), false /* ignoreVisibility */).toRect();
InsetUtils.addInsets(insets, getLetterboxInsets());
- return new RemoteAnimationTarget(task.mTaskId, record.getMode(),
- record.mAdapter.mCapturedLeash, !fillsParent(),
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(task.mTaskId,
+ record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(),
new Rect(), insets,
getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds,
record.mAdapter.mRootTaskBounds, task.getWindowConfiguration(),
false /*isNotInRecents*/,
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
+ target.hasAnimatingParent = record.hasAnimatingParent();
+ return target;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 71ac730..bab5a61 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.Manifest.permission.ACTIVITY_EMBEDDING;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.ActivityManager.START_ABORTED;
@@ -1953,38 +1952,43 @@
}
}
- if (mInTaskFragment != null && mInTaskFragment.getTask() != null) {
- final int hostUid = mInTaskFragment.getTask().effectiveUid;
- final int embeddingUid = targetTask != null ? targetTask.effectiveUid : r.getUid();
- if (!canTaskBeEmbedded(hostUid, embeddingUid)) {
- Slog.e(TAG, "Cannot embed activity to a task owned by " + hostUid + " targetTask= "
- + targetTask);
- return START_PERMISSION_DENIED;
- }
+ if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) {
+ Slog.e(TAG, "Permission denied: Cannot embed " + r + " to " + mInTaskFragment.getTask()
+ + " targetTask= " + targetTask);
+ return START_PERMISSION_DENIED;
}
return START_SUCCESS;
}
/**
- * Return {@code true} if the {@param task} can embed another task.
- * @param hostUid the uid of the host task
- * @param embeddedUid the uid of the task the are going to be embedded
+ * Return {@code true} if an activity can be embedded to the TaskFragment.
+ * @param taskFragment the TaskFragment for embedding.
+ * @param starting the starting activity.
+ * @param newTask whether the starting activity is going to be launched on a new task.
+ * @param targetTask the target task for launching activity, which could be different from
+ * the one who hosting the embedding.
*/
- private boolean canTaskBeEmbedded(int hostUid, int embeddedUid) {
+ private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, ActivityRecord starting,
+ boolean newTask, Task targetTask) {
+ final Task hostTask = taskFragment.getTask();
+ if (hostTask == null) {
+ return false;
+ }
+
// Allowing the embedding if the task is owned by system.
+ final int hostUid = hostTask.effectiveUid;
if (hostUid == Process.SYSTEM_UID) {
return true;
}
- // Allowing embedding if the host task is owned by an app that has the ACTIVITY_EMBEDDING
- // permission
- if (mService.checkPermission(ACTIVITY_EMBEDDING, -1, hostUid) == PERMISSION_GRANTED) {
- return true;
+ // Not allowed embedding an activity of another app.
+ if (hostUid != starting.getUid()) {
+ return false;
}
- // Allowing embedding if it is from the same app that owned the task
- return hostUid == embeddedUid;
+ // Not allowed embedding task.
+ return !newTask && (targetTask == null || targetTask == hostTask);
}
/**
@@ -2801,10 +2805,15 @@
newParent = mInTaskFragment;
}
} else {
- // Use the child TaskFragment (if any) as the new parent if the activity can be embedded
final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
false /* includingEmbeddedTask */);
- newParent = top != null ? top.getTaskFragment() : task;
+ final TaskFragment taskFragment = top != null ? top.getTaskFragment() : null;
+ if (taskFragment != null && taskFragment.isEmbedded()
+ && task.effectiveUid == mStartActivity.getUid()) {
+ // Use the embedded TaskFragment of the top activity as the new parent if the
+ // activity can be embedded.
+ newParent = top.getTaskFragment();
+ }
}
if (mStartActivity.getTaskFragment() == null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1c8f6f1..681ff90 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6554,7 +6554,8 @@
@Override
public PackageConfigurationUpdater createPackageConfigurationUpdater() {
- return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
+ return new PackageConfigurationUpdaterImpl(Binder.getCallingPid(),
+ ActivityTaskManagerService.this);
}
@Override
@@ -6571,57 +6572,4 @@
null /* trigger */, mRootWindowContainer.getDefaultDisplay());
}
}
-
- final class PackageConfigurationUpdaterImpl implements
- ActivityTaskManagerInternal.PackageConfigurationUpdater {
- private final int mPid;
- private Integer mNightMode;
- private LocaleList mLocales;
-
- PackageConfigurationUpdaterImpl(int pid) {
- mPid = pid;
- }
-
- @Override
- public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
- mNightMode = nightMode;
- return this;
- }
-
- @Override
- public ActivityTaskManagerInternal.PackageConfigurationUpdater
- setLocales(LocaleList locales) {
- mLocales = locales;
- return this;
- }
-
- @Override
- public void commit() {
- synchronized (mGlobalLock) {
- final long ident = Binder.clearCallingIdentity();
- try {
- final WindowProcessController wpc = mProcessMap.getProcess(mPid);
- if (wpc == null) {
- Slog.w(TAG, "Override application configuration: cannot find pid " + mPid);
- return;
- }
- LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists(
- mLocales, getGlobalConfiguration().getLocales());
- wpc.applyAppSpecificConfig(mNightMode, localesOverride);
- wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride);
- mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- Integer getNightMode() {
- return mNightMode;
- }
-
- LocaleList getLocales() {
- return mLocales;
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 7a42351..c0b6979 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -237,6 +237,8 @@
// Check if there is any override
if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
+ // Unfreeze the windows that were previously frozen for TaskFragment animation.
+ unfreezeEmbeddedChangingWindows();
overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
}
@@ -341,6 +343,9 @@
switch (changingType) {
case TYPE_TASK:
return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+ case TYPE_ACTIVITY:
+ // ActivityRecord is put in a change transition only when it is reparented
+ // to an organized TaskFragment. See ActivityRecord#shouldStartChangeTransition.
case TYPE_TASK_FRAGMENT:
return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
default:
@@ -511,6 +516,16 @@
: null;
}
+ private void unfreezeEmbeddedChangingWindows() {
+ final ArraySet<WindowContainer> changingContainers = mDisplayContent.mChangingContainers;
+ for (int i = changingContainers.size() - 1; i >= 0; i--) {
+ final WindowContainer wc = changingContainers.valueAt(i);
+ if (wc.isEmbedded()) {
+ wc.mSurfaceFreezer.unfreeze(wc.getSyncTransaction());
+ }
+ }
+ }
+
/**
* Overrides the pending transition with the remote animation defined by the
* {@link ITaskFragmentOrganizer} if all windows in the transition are children of
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 73d6cec..c9db14d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -444,7 +444,9 @@
}
if (mDisplayContent.mFixedRotationTransitionListener
- .isTopFixedOrientationRecentsAnimating()) {
+ .isTopFixedOrientationRecentsAnimating()
+ // If screen is off or the device is going to sleep, then still allow to update.
+ && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
// During the recents animation, the closing app might still be considered on top.
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 767e2c2..403df91 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -163,6 +163,7 @@
// animate-out as new one animates-in.
mWin.cancelAnimation();
mWin.mProvidedInsetsSources.remove(mSource.getType());
+ mSeamlessRotating = false;
}
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s", win,
InsetsState.typeToString(mSource.getType()));
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index c18c94d..45411a9 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -247,7 +247,8 @@
TapEventReceiver(InputChannel inputChannel, Context context) {
super(inputChannel, UiThread.getHandler().getLooper());
mDoubleTapListener = new DoubleTapListener();
- mDoubleTapDetector = new GestureDetector(context, mDoubleTapListener);
+ mDoubleTapDetector = new GestureDetector(
+ context, mDoubleTapListener, UiThread.getHandler());
}
@Override
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 505c4be..52eea4d 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -173,7 +173,7 @@
@GuardedBy("mLock")
void updateFromImpl(String packageName, int userId,
- ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
+ PackageConfigurationUpdaterImpl impl) {
synchronized (mLock) {
PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
if (impl.getNightMode() != null) {
diff --git a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
new file mode 100644
index 0000000..96025054
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
@@ -0,0 +1,86 @@
+/*
+ * 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.server.wm;
+
+import android.os.Binder;
+import android.os.LocaleList;
+import android.util.Slog;
+
+/**
+ * An implementation of {@link ActivityTaskManagerInternal.PackageConfigurationUpdater}.
+ */
+final class PackageConfigurationUpdaterImpl implements
+ ActivityTaskManagerInternal.PackageConfigurationUpdater {
+ private static final String TAG = "PackageConfigurationUpdaterImpl";
+ private final int mPid;
+ private Integer mNightMode;
+ private LocaleList mLocales;
+ private ActivityTaskManagerService mAtm;
+
+ PackageConfigurationUpdaterImpl(int pid, ActivityTaskManagerService atm) {
+ mPid = pid;
+ mAtm = atm;
+ }
+
+ @Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
+ synchronized (this) {
+ mNightMode = nightMode;
+ }
+ return this;
+ }
+
+ @Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater
+ setLocales(LocaleList locales) {
+ synchronized (this) {
+ mLocales = locales;
+ }
+ return this;
+ }
+
+ @Override
+ public void commit() {
+ synchronized (this) {
+ synchronized (mAtm.mGlobalLock) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ final WindowProcessController wpc = mAtm.mProcessMap.getProcess(mPid);
+ if (wpc == null) {
+ Slog.w(TAG, "Override application configuration: cannot find pid " + mPid);
+ return;
+ }
+ LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists(
+ mLocales, mAtm.getGlobalConfiguration().getLocales());
+ wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+ wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride);
+ mAtm.mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ }
+
+ Integer getNightMode() {
+ return mNightMode;
+ }
+
+ LocaleList getLocales() {
+ return mLocales;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
new file mode 100644
index 0000000..ef8dee4
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.wm;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import java.util.Set;
+
+/**
+ * Maintains a map of possible {@link DisplayInfo} for displays and states that may be encountered
+ * on a device. This is not guaranteed to include all possible device states for all displays.
+ *
+ * By 'possible', this class only handles device states for displays and display groups it is
+ * currently aware of. It can not handle all eventual states the system may enter, for example, if
+ * an external display is added, or a new display is added to the group.
+ */
+public class PossibleDisplayInfoMapper {
+ private static final String TAG = "PossibleDisplayInfoMapper";
+ private static final boolean DEBUG = false;
+
+ private final DisplayManagerInternal mDisplayManagerInternal;
+
+ /**
+ * Map of all logical displays, indexed by logical display id.
+ * Each logical display has multiple entries, one for each possible rotation and device
+ * state.
+ *
+ * Emptied and re-calculated when a display is added, removed, or changed.
+ */
+ private final SparseArray<Set<DisplayInfo>> mDisplayInfos = new SparseArray<>();
+
+ PossibleDisplayInfoMapper(DisplayManagerInternal displayManagerInternal) {
+ mDisplayManagerInternal = displayManagerInternal;
+ }
+
+
+ /**
+ * Returns, for the given displayId, a set of display infos. Set contains the possible rotations
+ * for each supported device state.
+ */
+ public Set<DisplayInfo> getPossibleDisplayInfos(int displayId) {
+ // Update display infos before returning, since any cached values would have been removed
+ // in response to any display event. This model avoids re-computing the cache for every
+ // display change event (which occurs extremely frequently in the normal usage of the
+ // device).
+ updatePossibleDisplayInfos(displayId);
+ if (!mDisplayInfos.contains(displayId)) {
+ return new ArraySet<>();
+ }
+ return Set.copyOf(mDisplayInfos.get(displayId));
+ }
+
+ /**
+ * Updates the possible {@link DisplayInfo}s for the given display, by calculating the
+ * DisplayInfo for each rotation across supported device states.
+ */
+ public void updatePossibleDisplayInfos(int displayId) {
+ Set<DisplayInfo> displayInfos = mDisplayManagerInternal.getPossibleDisplayInfo(displayId);
+ if (DEBUG) {
+ Slog.v(TAG, "updatePossibleDisplayInfos, calculate rotations for given DisplayInfo "
+ + displayInfos.size() + " on display " + displayId);
+ }
+ updateDisplayInfos(displayInfos);
+ }
+
+ /**
+ * For the given displayId, removes all possible {@link DisplayInfo}.
+ */
+ public void removePossibleDisplayInfos(int displayId) {
+ if (DEBUG && mDisplayInfos.get(displayId) != null) {
+ Slog.v(TAG, "onDisplayRemoved, remove all DisplayInfo (" + mDisplayInfos.get(
+ displayId).size() + ") with id " + displayId);
+ }
+ mDisplayInfos.remove(displayId);
+ }
+
+ private void updateDisplayInfos(Set<DisplayInfo> displayInfos) {
+ // Empty out cache before re-computing.
+ mDisplayInfos.clear();
+ DisplayInfo[] originalDisplayInfos = new DisplayInfo[displayInfos.size()];
+ displayInfos.toArray(originalDisplayInfos);
+ // Iterate over each logical display layout for the current state.
+ Set<DisplayInfo> rotatedDisplayInfos;
+ for (DisplayInfo di : originalDisplayInfos) {
+ rotatedDisplayInfos = new ArraySet<>();
+ // Calculate all possible rotations for each logical display.
+ for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
+ rotatedDisplayInfos.add(applyRotation(di, rotation));
+ }
+ // Combine all results under the logical display id.
+ Set<DisplayInfo> priorDisplayInfos = mDisplayInfos.get(di.displayId, new ArraySet<>());
+ priorDisplayInfos.addAll(rotatedDisplayInfos);
+ mDisplayInfos.put(di.displayId, priorDisplayInfos);
+ }
+ }
+
+ private static DisplayInfo applyRotation(DisplayInfo displayInfo,
+ @Surface.Rotation int rotation) {
+ DisplayInfo updatedDisplayInfo = new DisplayInfo();
+ updatedDisplayInfo.copyFrom(displayInfo);
+ updatedDisplayInfo.rotation = rotation;
+
+ final int naturalWidth = updatedDisplayInfo.getNaturalWidth();
+ final int naturalHeight = updatedDisplayInfo.getNaturalHeight();
+ updatedDisplayInfo.logicalWidth = naturalWidth;
+ updatedDisplayInfo.logicalHeight = naturalHeight;
+ return updatedDisplayInfo;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 6b57ede..b90f937 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -436,6 +436,19 @@
int getMode() {
return mMode;
}
+
+ /** Whether its parent is also an animation target in the same transition. */
+ boolean hasAnimatingParent() {
+ // mOpeningApps and mClosingApps are only activities, so only need to check
+ // mChangingContainers.
+ for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+ if (mWindowContainer.isDescendantOf(
+ mDisplayContent.mChangingContainers.valueAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
}
class RemoteAnimationAdapterWrapper implements AnimationAdapter {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c48dba4..4020788 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2577,6 +2577,9 @@
if (mService.isBooted() || mService.isBooting()) {
startSystemDecorations(display.mDisplayContent);
}
+ // Drop any cached DisplayInfos associated with this display id - the values are now
+ // out of date given this display added event.
+ mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
}
}
@@ -2597,8 +2600,8 @@
if (displayContent == null) {
return;
}
-
displayContent.remove();
+ mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
}
}
@@ -2610,6 +2613,9 @@
if (displayContent != null) {
displayContent.onDisplayChanged();
}
+ // Drop any cached DisplayInfos associated with this display id - the values are now
+ // out of date given this display changed event.
+ mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ca98564..a3626d2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -130,7 +130,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static java.lang.Integer.MAX_VALUE;
@@ -2984,16 +2983,6 @@
super.resetSurfacePositionForAnimationLeash(t);
}
- @Override
- Rect getAnimationBounds(int appRootTaskClipMode) {
- // TODO(b/131661052): we should remove appRootTaskClipMode with hierarchical animations.
- if (appRootTaskClipMode == ROOT_TASK_CLIP_BEFORE_ANIM && getRootTask() != null) {
- // Using the root task bounds here effectively applies the clipping before animation.
- return getRootTask().getBounds();
- }
- return super.getAnimationBounds(appRootTaskClipMode);
- }
-
boolean shouldAnimate() {
/**
* Animations are handled by the TaskOrganizer implementation.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c8b8b0d..a902ca9 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -33,7 +33,6 @@
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -465,7 +464,7 @@
return this;
}
- /** Returns {@code true} if this is a container for embedded activities or tasks. */
+ @Override
boolean isEmbedded() {
if (mIsEmbedded) {
return true;
@@ -2047,9 +2046,7 @@
if (shouldStartChangeTransition(mTmpPrevBounds)) {
initializeChangeTransition(mTmpPrevBounds);
- }
-
- if (mTaskFragmentOrganizer != null) {
+ } else if (mTaskFragmentOrganizer != null) {
// Update the surface position here instead of in the organizer so that we can make sure
// it can be synced with the surface freezer.
updateSurfacePosition(getSyncTransaction());
@@ -2073,15 +2070,6 @@
return !startBounds.equals(getBounds());
}
- /**
- * Initializes a change transition. See {@link SurfaceFreezer} for more information.
- */
- void initializeChangeTransition(Rect startBounds) {
- mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
- mDisplayContent.mChangingContainers.add(this);
- mSurfaceFreezer.freeze(getSyncTransaction(), startBounds);
- }
-
@Override
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
@@ -2163,6 +2151,11 @@
return mTaskFragmentOrganizer != null;
}
+ /** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */
+ final boolean isOrganizedTaskFragment() {
+ return mTaskFragmentOrganizer != null;
+ }
+
/** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
void clearLastPausedActivity() {
forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 63951d9..5ce9938 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -168,6 +168,9 @@
*/
@VisibleForTesting
void addSkipClosingAppSnapshotTasks(ArraySet<Task> tasks) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
mSkipClosingAppSnapshotTasks.addAll(tasks);
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index cdfa5aa..26fe2fc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -30,6 +31,7 @@
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -2578,13 +2580,34 @@
mSurfaceFreezer.unfreeze(getPendingTransaction());
}
+ /**
+ * Initializes a change transition. See {@link SurfaceFreezer} for more information.
+ *
+ * For now, this will only be called for the following cases:
+ * 1. {@link Task} is changing windowing mode between fullscreen and freeform.
+ * 2. {@link TaskFragment} is organized and is changing window bounds.
+ * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}.
+ *
+ * This shouldn't be called on other {@link WindowContainer} unless there is a valid use case.
+ */
+ void initializeChangeTransition(Rect startBounds) {
+ mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
+ mDisplayContent.mChangingContainers.add(this);
+ mSurfaceFreezer.freeze(getSyncTransaction(), startBounds);
+ }
+
ArraySet<WindowContainer> getAnimationSources() {
return mSurfaceAnimationSources;
}
@Override
public SurfaceControl getFreezeSnapshotTarget() {
- return null;
+ // Only allow freezing if this window is in a TRANSIT_CHANGE
+ if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)
+ || !mDisplayContent.mChangingContainers.contains(this)) {
+ return null;
+ }
+ return getSurfaceControl();
}
@Override
@@ -2793,7 +2816,8 @@
boolean isVoiceInteraction) {
if (isOrganized()
// TODO(b/161711458): Clean-up when moved to shell.
- && getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ && getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+ && getWindowingMode() != WINDOWING_MODE_FREEFORM) {
return null;
}
@@ -3189,6 +3213,11 @@
return false;
}
+ /** @return {@code true} if this is a container for embedded activities or tasks. */
+ boolean isEmbedded() {
+ return false;
+ }
+
/**
* @return {@code true} if this container's surface should be shown when it is created.
*/
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 907098e..b9c6e1b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -46,8 +46,6 @@
import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_270;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -174,8 +172,10 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
-import android.hardware.configstore.V1_0.ISurfaceFlingerConfigs;
import android.hardware.configstore.V1_0.OptionalBool;
+import android.hardware.configstore.V1_1.DisplayOrientation;
+import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
+import android.hardware.configstore.V1_1.OptionalDisplayOrientation;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManager;
@@ -227,6 +227,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import android.view.Display;
+import android.view.DisplayAddress;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IAppTransitionAnimationSpecsFuture;
@@ -326,13 +327,11 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -454,6 +453,8 @@
*/
static final boolean ENABLE_FIXED_ROTATION_TRANSFORM =
SystemProperties.getBoolean("persist.wm.fixed_rotation_transform", true);
+ private @Surface.Rotation int mPrimaryDisplayOrientation = Surface.ROTATION_0;
+ private DisplayAddress mPrimaryDisplayPhysicalAddress;
// Enums for animation scale update types.
@Retention(RetentionPolicy.SOURCE)
@@ -1054,6 +1055,10 @@
final HighRefreshRateDenylist mHighRefreshRateDenylist;
+ // Maintainer of a collection of all possible DisplayInfo for all configurations of the
+ // logical displays.
+ final PossibleDisplayInfoMapper mPossibleDisplayInfoMapper;
+
// If true, only the core apps and services are being launched because the device
// is in a special boot mode, such as being encrypted or waiting for a decryption password.
// For example, when this flag is true, there will be no wallpaper service.
@@ -1229,6 +1234,7 @@
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mPossibleDisplayInfoMapper = new PossibleDisplayInfoMapper(mDisplayManagerInternal);
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
@@ -2458,16 +2464,21 @@
configChanged = displayContent.updateOrientation();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- final DisplayInfo rotatedDisplayInfo =
- win.mToken.getFixedRotationTransformDisplayInfo();
- if (rotatedDisplayInfo != null) {
- outSurfaceControl.setTransformHint(rotatedDisplayInfo.rotation);
- } else {
- // We have to update the transform hint of display here, but we need to get if from
- // SurfaceFlinger, so set it as rotation of display for most cases, then
- // SurfaceFlinger would still update the transform hint of display in next frame.
- outSurfaceControl.setTransformHint(displayContent.getDisplayInfo().rotation);
+ final DisplayInfo displayInfo = win.getDisplayInfo();
+ int transformHint = displayInfo.rotation;
+ // If the window is on the primary display, use the panel orientation to adjust the
+ // transform hint
+ final boolean isPrimaryDisplay = displayInfo.address != null &&
+ displayInfo.address.equals(mPrimaryDisplayPhysicalAddress);
+ if (isPrimaryDisplay) {
+ transformHint = (transformHint + mPrimaryDisplayOrientation) % 4;
}
+ outSurfaceControl.setTransformHint(transformHint);
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Passing transform hint %d for window %s%s",
+ transformHint, win,
+ isPrimaryDisplay ? " on primary display with orientation "
+ + mPrimaryDisplayOrientation : "");
if (toBeDisplayed && win.mIsWallpaper) {
displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */);
@@ -4873,6 +4884,9 @@
mTaskSnapshotController.systemReady();
mHasWideColorGamutSupport = queryWideColorGamutSupport();
mHasHdrSupport = queryHdrSupport();
+ mPrimaryDisplayOrientation = queryPrimaryDisplayOrientation();
+ mPrimaryDisplayPhysicalAddress =
+ DisplayAddress.fromPhysicalDisplayId(SurfaceControl.getPrimaryPhysicalDisplayId());
UiThread.getHandler().post(mSettingsObserver::loadSettings);
IVrManager vrManager = IVrManager.Stub.asInterface(
ServiceManager.getService(Context.VR_SERVICE));
@@ -4892,6 +4906,9 @@
}
}
+
+ // Keep logic in sync with SurfaceFlingerProperties.cpp
+ // Consider exposing properties via ISurfaceComposer instead.
private static boolean queryWideColorGamutSupport() {
boolean defaultValue = false;
Optional<Boolean> hasWideColorProp = SurfaceFlingerProperties.has_wide_color_display();
@@ -4932,6 +4949,39 @@
return false;
}
+ private static @Surface.Rotation int queryPrimaryDisplayOrientation() {
+ Optional<SurfaceFlingerProperties.primary_display_orientation_values> prop =
+ SurfaceFlingerProperties.primary_display_orientation();
+ if (prop.isPresent()) {
+ switch (prop.get()) {
+ case ORIENTATION_90: return Surface.ROTATION_90;
+ case ORIENTATION_180: return Surface.ROTATION_180;
+ case ORIENTATION_270: return Surface.ROTATION_270;
+ case ORIENTATION_0:
+ default:
+ return Surface.ROTATION_0;
+ }
+ }
+ try {
+ ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService();
+ OptionalDisplayOrientation primaryDisplayOrientation =
+ surfaceFlinger.primaryDisplayOrientation();
+ if (primaryDisplayOrientation != null && primaryDisplayOrientation.specified) {
+ switch (primaryDisplayOrientation.value) {
+ case DisplayOrientation.ORIENTATION_90: return Surface.ROTATION_90;
+ case DisplayOrientation.ORIENTATION_180: return Surface.ROTATION_180;
+ case DisplayOrientation.ORIENTATION_270: return Surface.ROTATION_270;
+ case DisplayOrientation.ORIENTATION_0:
+ default:
+ return Surface.ROTATION_0;
+ }
+ }
+ } catch (Exception e) {
+ // Use default value if we can't talk to config store.
+ }
+ return Surface.ROTATION_0;
+ }
+
void reportFocusChanged(IBinder oldToken, IBinder newToken) {
WindowState lastFocus;
WindowState newFocus;
@@ -8467,23 +8517,10 @@
+ " for getPossibleMaximumWindowMetrics");
return new ArrayList<>();
}
- // TODO(181127261) DisplayInfo should be pushed from DisplayManager.
- final DisplayContent dc = mRoot.getDisplayContent(displayId);
- if (dc == null) {
- Slog.e(TAG, "Invalid displayId " + displayId
- + " for getPossibleMaximumWindowMetrics");
- return new ArrayList<>();
- }
- // TODO(181127261) DisplayManager should provide a DisplayInfo for each rotation
- DisplayInfo currentDisplayInfo = dc.getDisplayInfo();
- Set<DisplayInfo> displayInfoSet = new HashSet<>();
- for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
- currentDisplayInfo.rotation = rotation;
- // TODO(181127261) Retrieve the device state from display stack.
- displayInfoSet.add(new DisplayInfo(currentDisplayInfo));
- }
- return new ArrayList<DisplayInfo>(displayInfoSet);
+ // Retrieve the DisplayInfo for all possible rotations across all possible display
+ // layouts.
+ return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId));
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a0dc247..d4d8971 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -80,16 +80,10 @@
static final int ROOT_TASK_CLIP_AFTER_ANIM = 0;
/**
- * Mode how the window gets clipped by the root task bounds: The clipping should be applied
- * before applying the animation transformation, i.e. the root task bounds move with the window.
- */
- static final int ROOT_TASK_CLIP_BEFORE_ANIM = 1;
-
- /**
* Mode how window gets clipped by the root task bounds during an animation: Don't clip the
* window by the root task bounds.
*/
- static final int ROOT_TASK_CLIP_NONE = 2;
+ static final int ROOT_TASK_CLIP_NONE = 1;
// Unchanging local convenience fields.
final WindowManagerService mService;
@@ -256,10 +250,10 @@
// away.
if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) {
mPostDrawTransaction.merge(postDrawTransaction);
- layoutNeeded = true;
} else {
mWin.getSyncTransaction().merge(postDrawTransaction);
}
+ layoutNeeded = true;
}
return layoutNeeded;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index b1b6e53..3fcd38b6 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -18,6 +18,9 @@
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
@@ -31,13 +34,12 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import static org.mockito.Mockito.mock;
-
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowProcessController;
@@ -181,8 +183,10 @@
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE);
- mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
+ mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE});
flushHandler();
// The current committed and requests states do not change because the current state remains
@@ -190,9 +194,10 @@
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE);
assertArrayEquals(callback.getLastNotifiedInfo().supportedStates,
- new int[] { DEFAULT_DEVICE_STATE.getIdentifier() });
+ new int[]{DEFAULT_DEVICE_STATE.getIdentifier()});
}
@Test
@@ -207,9 +212,11 @@
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE);
- mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE });
+ mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE});
flushHandler();
// The current committed and requests states do not change because the current state remains
@@ -217,6 +224,8 @@
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE);
// The callback wasn't notified about a change in supported states as the states have not
// changed.
@@ -335,6 +344,7 @@
DEFAULT_DEVICE_STATE.getIdentifier());
}
+ @FlakyTest(bugId = 200332057)
@Test
public void requestState_pendingStateAtRequest() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
@@ -466,6 +476,7 @@
OTHER_DEVICE_STATE.getIdentifier());
}
+ @FlakyTest(bugId = 200332057)
@Test
public void requestState_becomesUnsupported() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 8279624..fbc1952 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -16,12 +16,17 @@
package com.android.server.display;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.eq;
@@ -55,6 +60,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
+import java.util.Set;
@SmallTest
@Presubmit
@@ -123,14 +129,14 @@
// add
LogicalDisplay displayAdded = add(device);
assertEquals(info(displayAdded).address, info(device).address);
- assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+ assertEquals(DEFAULT_DISPLAY, id(displayAdded));
// remove
mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
verify(mListenerMock).onLogicalDisplayEventLocked(
mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
- assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+ assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
assertEquals(displayAdded, displayRemoved);
}
@@ -155,11 +161,11 @@
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
- assertNotEquals(Display.DEFAULT_DISPLAY, id(display1));
+ assertNotEquals(DEFAULT_DISPLAY, id(display1));
LogicalDisplay display2 = add(device2);
assertEquals(info(display2).address, info(device2).address);
- assertEquals(Display.DEFAULT_DISPLAY, id(display2));
+ assertEquals(DEFAULT_DISPLAY, id(display2));
}
@Test
@@ -171,12 +177,12 @@
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
- assertEquals(Display.DEFAULT_DISPLAY, id(display1));
+ assertEquals(DEFAULT_DISPLAY, id(display1));
LogicalDisplay display2 = add(device2);
assertEquals(info(display2).address, info(device2).address);
// Despite the flags, we can only have one default display
- assertNotEquals(Display.DEFAULT_DISPLAY, id(display2));
+ assertNotEquals(DEFAULT_DISPLAY, id(display2));
}
@Test
@@ -189,7 +195,67 @@
int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID);
assertEquals(3, ids.length);
Arrays.sort(ids);
- assertEquals(Display.DEFAULT_DISPLAY, ids[0]);
+ assertEquals(DEFAULT_DISPLAY, ids[0]);
+ }
+
+ @Test
+ public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() {
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+
+ Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfos.size()).isEqualTo(3);
+ for (DisplayInfo displayInfo : displayInfos) {
+ assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfo.logicalWidth).isAnyOf(600, 200, 700);
+ assertThat(displayInfo.logicalHeight).isEqualTo(800);
+ }
+ }
+
+ @Test
+ public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() {
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+
+ Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfos.size()).isEqualTo(2);
+ for (DisplayInfo displayInfo : displayInfos) {
+ assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfo.logicalWidth).isAnyOf(600, 200);
+ assertThat(displayInfo.logicalHeight).isEqualTo(800);
+ }
+ }
+
+ @Test
+ public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() {
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP));
+
+ Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfos.size()).isEqualTo(2);
+ for (DisplayInfo displayInfo : displayInfos) {
+ assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfo.logicalWidth).isAnyOf(600, 200);
+ assertThat(displayInfo.logicalHeight).isEqualTo(800);
+ }
}
@Test
@@ -199,11 +265,11 @@
LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
}
@@ -218,11 +284,11 @@
DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
LogicalDisplay display3 = add(device3);
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
- assertNotEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertNotEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
// Now switch it back to the default group by removing the flag and issuing an update
@@ -231,7 +297,7 @@
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
// Verify the new group is correct.
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
}
@@ -287,14 +353,14 @@
// add
LogicalDisplay displayAdded = add(device);
assertEquals(info(displayAdded).address, info(device).address);
- assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+ assertNotEquals(DEFAULT_DISPLAY, id(displayAdded));
// remove
mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
verify(mListenerMock).onLogicalDisplayEventLocked(
mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
- assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+ assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index b4fbf5f..184ea52 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -110,18 +110,20 @@
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
- t.setVisibility(leash, true /* visible */).apply();
+ t.show(leash).apply();
}
int cookieIndex = -1;
if (trampoline.equals(taskInfo.baseActivity)) {
cookieIndex = 0;
} else if (main.equals(taskInfo.baseActivity)) {
cookieIndex = 1;
- mainLatch.countDown();
}
if (cookieIndex >= 0) {
appearedCookies[cookieIndex] = taskInfo.launchCookies.isEmpty()
? null : taskInfo.launchCookies.get(0);
+ if (cookieIndex == 1) {
+ mainLatch.countDown();
+ }
}
}
};
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 9a14e7d..51cc318 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
@@ -83,7 +82,6 @@
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
import static com.google.common.truth.Truth.assertThat;
@@ -2779,10 +2777,36 @@
assertEquals(taskBounds, activity.getAnimationBounds(ROOT_TASK_CLIP_AFTER_ANIM));
assertEquals(new Point(0, 0), animationPosition);
+ }
- // ROOT_TASK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later.
- task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- assertEquals(rootTask.getBounds(), activity.getAnimationBounds(ROOT_TASK_CLIP_BEFORE_ANIM));
+ @Test
+ public void testTransitionAnimationBounds_returnTaskFragment() {
+ removeGlobalMinSizeRestriction();
+ final Task task = new TaskBuilder(mSupervisor).setCreateParentTask(true).build();
+ final Task rootTask = task.getRootTask();
+ final TaskFragment taskFragment = createTaskFragmentWithParentTask(task,
+ false /* createEmbeddedTask */);
+ final ActivityRecord activity = taskFragment.getTopNonFinishingActivity();
+ final Rect stackBounds = new Rect(0, 0, 1000, 600);
+ final Rect taskBounds = new Rect(100, 400, 600, 800);
+ final Rect taskFragmentBounds = new Rect(100, 400, 300, 800);
+ final Rect activityBounds = new Rect(100, 400, 300, 600);
+ // Set the bounds and windowing mode to window configuration directly, otherwise the
+ // testing setups may be discarded by configuration resolving.
+ rootTask.getWindowConfiguration().setBounds(stackBounds);
+ task.getWindowConfiguration().setBounds(taskBounds);
+ taskFragment.getWindowConfiguration().setBounds(taskFragmentBounds);
+ activity.getWindowConfiguration().setBounds(activityBounds);
+
+ // Check that anim bounds for freeform window match task fragment bounds
+ task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(taskFragment.getBounds(), activity.getAnimationBounds(ROOT_TASK_CLIP_NONE));
+
+ // ROOT_TASK_CLIP_AFTER_ANIM should use task fragment bounds since they will be clipped by
+ // bounds animation layer.
+ task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertEquals(taskFragment.getBounds(),
+ activity.getAnimationBounds(ROOT_TASK_CLIP_AFTER_ANIM));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index a0a3ce7..405d714 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -43,6 +43,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -54,6 +55,8 @@
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
@@ -391,6 +394,35 @@
mDc.mAppTransition.getAnimationStyleResId(attrs));
}
+ @Test
+ public void testActivityRecordReparentToTaskFragment() {
+ final ActivityRecord activity = createActivityRecord(mDc);
+ activity.setVisibility(true);
+ final Task task = activity.getTask();
+
+ // Add a TaskFragment of half of the Task size.
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer iOrganizer =
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(organizer)
+ .build();
+ final Rect taskBounds = new Rect();
+ task.getBounds(taskBounds);
+ taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
+
+ assertTrue(mDc.mChangingContainers.isEmpty());
+ assertFalse(mDc.mAppTransition.isTransitionSet());
+
+ // Schedule app transition when reparent activity to a TaskFragment of different size.
+ activity.reparent(taskFragment, POSITION_TOP);
+
+ assertTrue(mDc.mChangingContainers.contains(activity));
+ assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
+ }
+
private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
boolean mCancelled = false;
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7829362..e340217 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1572,6 +1572,12 @@
mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
assertTrue(displayRotation.updateRotationUnchecked(false));
+ // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
+ mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
+ displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
+ ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
+ assertTrue(displayRotation.updateRotationUnchecked(false));
+
// Rotation can be updated if the recents animation is animating but it is not on top, e.g.
// switching activities in different orientations by quickstep gesture.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java
new file mode 100644
index 0000000..6e00568
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java
@@ -0,0 +1,182 @@
+/*
+ * 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.server.wm;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRESENTATION;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+
+/**
+ * Tests for {@link PossibleDisplayInfoMapper}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:PossibleDisplayInfoMapperTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PossibleDisplayInfoMapperTests extends WindowTestsBase {
+
+ private PossibleDisplayInfoMapper mDisplayInfoMapper;
+ private final Set<DisplayInfo> mPossibleDisplayInfo = new ArraySet<>();
+ private DisplayInfo mDefaultDisplayInfo;
+ private DisplayInfo mSecondDisplayInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ mDisplayInfoMapper = mWm.mPossibleDisplayInfoMapper;
+ final DisplayInfo baseDisplayInfo = mWm.mRoot.getDisplayContent(
+ DEFAULT_DISPLAY).getDisplayInfo();
+ when(mWm.mDisplayManagerInternal.getPossibleDisplayInfo(anyInt())).thenReturn(
+ mPossibleDisplayInfo);
+
+ mDefaultDisplayInfo = new DisplayInfo(baseDisplayInfo);
+ initializeDisplayInfo(mDefaultDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 500, 800));
+ mSecondDisplayInfo = new DisplayInfo(baseDisplayInfo);
+ // Use the same display id for any display in the same group, due to the assumption that
+ // any display in the same grouped can be swapped out for each other (while maintaining the
+ // display id).
+ initializeDisplayInfo(mSecondDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 600, 1600));
+ mSecondDisplayInfo.flags |= FLAG_PRESENTATION;
+ }
+
+ @Test
+ public void testInitialization_isEmpty() {
+ // Empty after initializing.
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty();
+
+ // Still empty after updating.
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty();
+ }
+
+ @Test
+ public void testUpdatePossibleDisplayInfos_singleDisplay() {
+ mPossibleDisplayInfo.add(mDefaultDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ // An entry for each possible rotation, for a display that can be in a single state.
+ assertThat(displayInfos.size()).isEqualTo(4);
+ assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo);
+ }
+
+ @Test
+ public void testUpdatePossibleDisplayInfos_secondDisplayAdded_sameGroup() {
+ mPossibleDisplayInfo.add(mDefaultDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4);
+
+ // Add another display layout to the set of supported states.
+ mPossibleDisplayInfo.add(mSecondDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ Set<DisplayInfo> defaultDisplayInfos = new ArraySet<>();
+ Set<DisplayInfo> secondDisplayInfos = new ArraySet<>();
+ for (DisplayInfo di : displayInfos) {
+ if ((di.flags & FLAG_PRESENTATION) != 0) {
+ secondDisplayInfos.add(di);
+ } else {
+ defaultDisplayInfos.add(di);
+ }
+ }
+ // An entry for each possible rotation, for the default display.
+ assertThat(defaultDisplayInfos).hasSize(4);
+ assertPossibleDisplayInfoEntries(defaultDisplayInfos, mDefaultDisplayInfo);
+
+ // An entry for each possible rotation, for the second display.
+ assertThat(secondDisplayInfos).hasSize(4);
+ assertPossibleDisplayInfoEntries(secondDisplayInfos, mSecondDisplayInfo);
+ }
+
+ @Test
+ public void testUpdatePossibleDisplayInfos_secondDisplayAdded_differentGroup() {
+ mPossibleDisplayInfo.add(mDefaultDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4);
+
+ // Add another display to a different group.
+ mSecondDisplayInfo.displayId = DEFAULT_DISPLAY + 1;
+ mSecondDisplayInfo.displayGroupId = mDefaultDisplayInfo.displayGroupId + 1;
+ mPossibleDisplayInfo.add(mSecondDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId);
+
+ Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ // An entry for each possible rotation, for the default display.
+ assertThat(displayInfos).hasSize(4);
+ assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo);
+
+ Set<DisplayInfo> secondStateEntries =
+ mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId);
+ // An entry for each possible rotation, for the second display.
+ assertThat(secondStateEntries).hasSize(4);
+ assertPossibleDisplayInfoEntries(secondStateEntries, mSecondDisplayInfo);
+ }
+
+ private static void initializeDisplayInfo(DisplayInfo outDisplayInfo, int displayId,
+ Rect logicalBounds) {
+ outDisplayInfo.displayId = displayId;
+ outDisplayInfo.rotation = ROTATION_0;
+ outDisplayInfo.logicalWidth = logicalBounds.width();
+ outDisplayInfo.logicalHeight = logicalBounds.height();
+ }
+
+ private static void assertPossibleDisplayInfoEntries(Set<DisplayInfo> displayInfos,
+ DisplayInfo expectedDisplayInfo) {
+ boolean[] seenEveryRotation = new boolean[4];
+ for (DisplayInfo displayInfo : displayInfos) {
+ final int rotation = displayInfo.rotation;
+ seenEveryRotation[rotation] = true;
+ assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId);
+ assertEqualsRotatedDisplayInfo(displayInfo, expectedDisplayInfo);
+ }
+ assertThat(seenEveryRotation).isEqualTo(new boolean[]{true, true, true, true});
+ }
+
+ private static void assertEqualsRotatedDisplayInfo(DisplayInfo actual, DisplayInfo expected) {
+ if (actual.rotation == ROTATION_0 || actual.rotation == ROTATION_180) {
+ assertThat(actual.logicalWidth).isEqualTo(expected.logicalWidth);
+ assertThat(actual.logicalHeight).isEqualTo(expected.logicalHeight);
+ } else {
+ assertThat(actual.logicalWidth).isEqualTo(expected.logicalHeight);
+ assertThat(actual.logicalHeight).isEqualTo(expected.logicalWidth);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 348472a..6407c92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1171,7 +1171,6 @@
final WindowState w = addWindowToActivity(mActivity);
// Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}.
mActivity.mRootWindowContainer.performSurfacePlacement();
- mActivity.layoutLetterbox(null);
// The letterbox insets should be [450, 0 - 250, 0].
assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
mActivity.getLetterboxInsets());
@@ -1833,9 +1832,6 @@
}
private void assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(Rect parentBounds) {
- // Ensure Letterbox is updated.
- mActivity.layoutLetterbox(null);
-
// Letterbox should fill the gap between the parent bounds and the letterboxed activity.
final Rect letterboxedBounds = new Rect(mActivity.getBounds());
assertTrue(parentBounds.contains(letterboxedBounds));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
index e970c2a..e2f1334 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -19,7 +19,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
import static org.mockito.ArgumentMatchers.any;
@@ -89,43 +88,6 @@
}
@Test
- public void testApply_clipBeforeNoAnimationBounds() {
- // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0)
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
- argThat(rect -> rect.equals(mStackBounds)));
- }
-
- @Test
- public void testApply_clipBeforeNoStackBounds() {
- // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20)
- Rect windowCrop = new Rect(0, 0, 20, 20);
- Animation a = createClipRectAnimation(windowCrop, windowCrop);
- a.initialize(0, 0, 0, 0);
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
- null, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
- }
-
- @Test
- public void testApply_setCornerRadius() {
- final float windowCornerRadius = 30f;
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, windowCornerRadius);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction, never()).setCornerRadius(eq(mSurfaceControl), eq(windowCornerRadius));
- when(mAnimation.hasRoundedCorners()).thenReturn(true);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setCornerRadius(eq(mSurfaceControl), eq(windowCornerRadius));
- }
-
- @Test
public void testApply_setCornerRadius_noClip() {
final float windowCornerRadius = 30f;
WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
@@ -136,32 +98,6 @@
verify(mTransaction, never()).setCornerRadius(any(), anyFloat());
}
- @Test
- public void testApply_clipBeforeSmallerAnimationClip() {
- // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5)
- Rect windowCrop = new Rect(0, 0, 5, 5);
- Animation a = createClipRectAnimation(windowCrop, windowCrop);
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
- argThat(rect -> rect.equals(windowCrop)));
- }
-
- @Test
- public void testApply_clipBeforeSmallerStackClip() {
- // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20)
- Rect windowCrop = new Rect(0, 0, 20, 20);
- Animation a = createClipRectAnimation(windowCrop, windowCrop);
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
- argThat(rect -> rect.equals(mStackBounds)));
- }
-
private Animation createClipRectAnimation(Rect fromClip, Rect toClip) {
Animation a = new ClipRectAnimation(fromClip, toClip);
a.initialize(0, 0, 0, 0);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index fe1b1cd..b37c404 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.content.ComponentName
+import android.os.SystemProperties
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -43,6 +44,7 @@
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,6 +63,8 @@
class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+ private val isShellTransitionsEnabled =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
@@ -122,15 +126,17 @@
@Presubmit
@Test
- fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(true)
+ fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
@Presubmit
@Test
- fun imeAppWindowVisibility() {
- // the app starts visible in live tile, then becomes invisible during animation and
- // is again launched. Since we log 1x per frame, sometimes the activity visibility and
- // the app visibility are updated together, sometimes not, thus ignore activity check
- // at the start
+ fun imeAppWindowVisibilityLegacy() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ // the app starts visible in live tile, and stays visible for the duration of entering
+ // and exiting overview. However, legacy transitions seem to have a bug which causes
+ // everything to restart during the test, so expect the app to disappear and come back.
+ // Since we log 1x per frame, sometimes the activity visibility and the app visibility
+ // are updated together, sometimes not, thus ignore activity check at the start
testSpec.assertWm {
this.isAppWindowVisible(testApp.component, ignoreActivity = true)
.then()
@@ -142,6 +148,19 @@
@Presubmit
@Test
+ fun imeAppWindowVisibility() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ // the app starts visible in live tile, and stays visible for the duration of entering
+ // and exiting overview. Since we log 1x per frame, sometimes the activity visibility
+ // and the app visibility are updated together, sometimes not, thus ignore activity
+ // check at the start
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp.component, ignoreActivity = true)
+ }
+ }
+
+ @Presubmit
+ @Test
// During testing the launcher is always in portrait mode
fun entireScreenCovered() = testSpec.entireScreenCovered()
@@ -155,7 +174,8 @@
@Presubmit
@Test
- fun imeLayerIsBecomesVisible() {
+ fun imeLayerIsBecomesVisibleLegacy() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
testSpec.assertLayers {
this.isVisible(WindowManagerStateHelper.IME_COMPONENT)
.then()
@@ -167,6 +187,15 @@
@Presubmit
@Test
+ fun imeLayerIsBecomesVisible() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ testSpec.assertLayers {
+ this.isVisible(WindowManagerStateHelper.IME_COMPONENT)
+ }
+ }
+
+ @Presubmit
+ @Test
fun appLayerReplacesLauncher() {
testSpec.assertLayers {
this.isVisible(LAUNCHER_COMPONENT)
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 0f84f6e..c9a8947a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -322,6 +322,7 @@
triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
verify(mSafeModeTimeoutAlarm).cancel();
assertFalse(mGatewayConnection.isInSafeMode());
+ verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */);
}
@Test
@@ -391,6 +392,7 @@
triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
+ verifySafeModeStateAndCallbackFired(2 /* invocationCount */, false /* isInSafeMode */);
assertFalse(mGatewayConnection.isInSafeMode());
}
@@ -400,7 +402,7 @@
mTestLooper.dispatchAll();
triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
- assertFalse(mGatewayConnection.isInSafeMode());
+ verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */);
// Trigger a failed validation, and the subsequent safemode timeout.
triggerValidation(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
@@ -416,7 +418,7 @@
runnableCaptor.getValue().run();
mTestLooper.dispatchAll();
- assertTrue(mGatewayConnection.isInSafeMode());
+ verifySafeModeStateAndCallbackFired(2 /* invocationCount */, true /* isInSafeMode */);
}
private Consumer<VcnNetworkAgent> setupNetworkAndGetUnwantedCallback() {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index a696b3a..64d0bca 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -301,6 +300,11 @@
expectCanceled);
}
+ protected void verifySafeModeStateAndCallbackFired(int invocationCount, boolean isInSafeMode) {
+ verify(mGatewayStatusCallback, times(invocationCount)).onSafeModeStatusChanged();
+ assertEquals(isInSafeMode, mGatewayConnection.isInSafeMode());
+ }
+
protected void verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
@NonNull State expectedState) {
// Set a VcnNetworkAgent, and expect it to be unregistered and cleared
@@ -314,9 +318,8 @@
delayedEvent.run();
mTestLooper.dispatchAll();
- verify(mGatewayStatusCallback).onSafeModeStatusChanged();
assertEquals(expectedState, mGatewayConnection.getCurrentState());
- assertTrue(mGatewayConnection.isInSafeMode());
+ verifySafeModeStateAndCallbackFired(1, true);
verify(mockNetworkAgent).unregister();
assertNull(mGatewayConnection.getNetworkAgent());