Merge "Moves quick affordances into domain layer." into tm-qpr-dev
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 58ddd49..13934e5 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -310,6 +310,21 @@
public static final String MODULE_TELEPHONY = "telephony";
/**
+ * Constants that affect retries when the process is unable to write the property.
+ * The first constant is the number of times the process will attempt to set the
+ * property. The second constant is the delay between attempts.
+ */
+
+ /**
+ * Wait 200ms between retry attempts and the retry limit is 5. That gives a total possible
+ * delay of 1s, which should be less than ANR timeouts. The goal is to have the system crash
+ * because the property could not be set (which is a condition that is easily recognized) and
+ * not crash because of an ANR (which can be confusing to debug).
+ */
+ private static final int PROPERTY_FAILURE_RETRY_DELAY_MILLIS = 200;
+ private static final int PROPERTY_FAILURE_RETRY_LIMIT = 5;
+
+ /**
* Construct a system property that matches the rules described above. The module is
* one of the permitted values above. The API is a string that is a legal Java simple
* identifier. The api is modified to conform to the system property style guide by
@@ -670,7 +685,33 @@
}
}
}
- SystemProperties.set(name, Long.toString(val));
+ RuntimeException failure = null;
+ for (int attempt = 0; attempt < PROPERTY_FAILURE_RETRY_LIMIT; attempt++) {
+ try {
+ SystemProperties.set(name, Long.toString(val));
+ if (attempt > 0) {
+ // This log is not guarded. Based on known bug reports, it should
+ // occur once a week or less. The purpose of the log message is to
+ // identify the retries as a source of delay that might be otherwise
+ // be attributed to the cache itself.
+ Log.w(TAG, "Nonce set after " + attempt + " tries");
+ }
+ return;
+ } catch (RuntimeException e) {
+ if (failure == null) {
+ failure = e;
+ }
+ try {
+ Thread.sleep(PROPERTY_FAILURE_RETRY_DELAY_MILLIS);
+ } catch (InterruptedException x) {
+ // Ignore this exception. The desired delay is only approximate and
+ // there is no issue if the sleep sometimes terminates early.
+ }
+ }
+ }
+ // This point is reached only if SystemProperties.set() fails at least once.
+ // Rethrow the first exception that was received.
+ throw failure;
}
// Set the nonce in a static context. No handle is available.
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 0f1b39c..df1c0d7 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -892,7 +892,7 @@
* <tr><th colspan="7">Preview stabilization guaranteed stream configurations</th></tr>
* <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th rowspan="2">Sample use case(s)</th> </tr>
* <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr>
- * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td><td colspan="4" id="rb"></td> <td>Stabilized preview, GPU video processing, or no-preview stabilized video recording.</td> </tr>
+ * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td><td colspan="2" id="rb"></td> <td>Stabilized preview, GPU video processing, or no-preview stabilized video recording.</td> </tr>
* <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td> <td>{@code JPEG / YUV}</td><td id="rb">{@code MAXIMUM }</td><td>Standard still imaging with stabilized preview.</td> </tr>
* <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p }</td><td>High-resolution recording with stabilized preview and recording stream.</td> </tr>
* </table><br>
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 13a3ec8..fd4b94a 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -623,7 +623,7 @@
private static final List<String> PUBLIC_NAMESPACES =
Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL,
- NAMESPACE_DEVICE_POLICY_MANAGER);
+ NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE);
/**
* Privacy related properties definitions.
*
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index e246634..2c2ae06 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -45,6 +45,30 @@
public abstract class ViewStructure {
/**
+ * Key used for writing active child view information to the content capture bundle.
+ *
+ * The value stored under this key will be an ordered list of Autofill IDs of child views.
+ *
+ * TODO(b/241498401): Add @TestApi in Android U
+ * @hide
+ */
+ public static final String EXTRA_ACTIVE_CHILDREN_IDS =
+ "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS";
+
+ /**
+ * Key used for writing the first active child's position to the content capture bundle.
+ *
+ * When active child view information is provided under the
+ * {@link #EXTRA_ACTIVE_CHILDREN_IDS}, the value stored under this key will be the
+ * 0-based position of the first child view in the list relative to the positions of child views
+ * in the containing View's dataset.
+ *
+ * TODO(b/241498401): Add @TestApi in Android U
+ * @hide */
+ public static final String EXTRA_FIRST_ACTIVE_POSITION =
+ "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION";
+
+ /**
* Set the identifier for this view.
*
* @param id The view's identifier, as per {@link View#getId View.getId()}.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 63d42c0..67352c0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4396,15 +4396,42 @@
changes |= LAYOUT_CHANGED;
}
- if (!Arrays.equals(paramsForRotation, o.paramsForRotation)) {
+ if (paramsForRotation != o.paramsForRotation) {
+ if ((changes & LAYOUT_CHANGED) == 0) {
+ if (paramsForRotation != null && o.paramsForRotation != null
+ && paramsForRotation.length == o.paramsForRotation.length) {
+ for (int i = paramsForRotation.length - 1; i >= 0; i--) {
+ if (hasLayoutDiff(paramsForRotation[i], o.paramsForRotation[i])) {
+ changes |= LAYOUT_CHANGED;
+ break;
+ }
+ }
+ } else {
+ changes |= LAYOUT_CHANGED;
+ }
+ }
paramsForRotation = o.paramsForRotation;
checkNonRecursiveParams();
- changes |= LAYOUT_CHANGED;
}
return changes;
}
+ /**
+ * Returns {@code true} if the 2 params may have difference results of
+ * {@link WindowLayout#computeFrames}.
+ */
+ private static boolean hasLayoutDiff(LayoutParams a, LayoutParams b) {
+ return a.width != b.width || a.height != b.height || a.x != b.x || a.y != b.y
+ || a.horizontalMargin != b.horizontalMargin
+ || a.verticalMargin != b.verticalMargin
+ || a.layoutInDisplayCutoutMode != b.layoutInDisplayCutoutMode
+ || a.gravity != b.gravity || !Arrays.equals(a.providedInsets, b.providedInsets)
+ || a.mFitInsetsTypes != b.mFitInsetsTypes
+ || a.mFitInsetsSides != b.mFitInsetsSides
+ || a.mFitInsetsIgnoringVisibility != b.mFitInsetsIgnoringVisibility;
+ }
+
@Override
public String debug(String output) {
output += "Contents of " + this + ":";
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index ba4176f..db4ac5d 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -55,6 +55,15 @@
/**
* Called when a node has been added to the screen and is visible to the user.
*
+ * On API level 33, this event may be re-sent with additional information if a view's children
+ * have changed, e.g. scrolling Views inside of a ListView. This information will be stored in
+ * the extras Bundle associated with the event's ViewNode. Within the Bundle, the
+ * "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" key may be used to get a list of
+ * Autofill IDs of active child views, and the
+ * "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" key may be used to get the 0-based
+ * position of the first active child view in the list relative to the positions of child views
+ * in the container View's dataset.
+ *
* <p>The metadata of the node is available through {@link #getViewNode()}.
*/
public static final int TYPE_VIEW_APPEARED = 1;
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 48d2970..1664637 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -280,6 +280,15 @@
"service_explicitly_enabled";
/**
+ * Device config property used by {@code android.widget.AbsListView} to determine whether or
+ * not it should report the positions of its children to Content Capture.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN =
+ "report_list_view_children";
+
+ /**
* Maximum number of events that are buffered before sent to the app.
*
* @hide
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 231ae08..0b0bfb1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -20,6 +20,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -37,6 +38,7 @@
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.Trace;
+import android.provider.DeviceConfig;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
@@ -65,6 +67,7 @@
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
+import android.view.ViewStructure;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -73,6 +76,9 @@
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
@@ -634,6 +640,23 @@
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
+ * Indicates that reporting positions of child views to content capture is enabled via
+ * DeviceConfig.
+ */
+ private static boolean sContentCaptureReportingEnabledByDeviceConfig = false;
+
+ /**
+ * Listens for changes to DeviceConfig properties and updates stored values accordingly.
+ */
+ private static DeviceConfig.OnPropertiesChangedListener sDeviceConfigChangeListener = null;
+
+ /**
+ * Indicates that child positions of views should be reported to Content Capture the next time
+ * that active views are refreshed.
+ */
+ private boolean mReportChildrenToContentCaptureOnNextUpdate = true;
+
+ /**
* Helper object that renders and controls the fast scroll thumb.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768941)
@@ -850,8 +873,44 @@
public void adjustListItemSelectionBounds(Rect bounds);
}
+ private static class DeviceConfigChangeListener
+ implements DeviceConfig.OnPropertiesChangedListener {
+ @Override
+ public void onPropertiesChanged(
+ @NonNull DeviceConfig.Properties properties) {
+ if (!DeviceConfig.NAMESPACE_CONTENT_CAPTURE.equals(properties.getNamespace())) {
+ return;
+ }
+
+ for (String key : properties.getKeyset()) {
+ if (!ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN
+ .equals(key)) {
+ continue;
+ }
+
+ sContentCaptureReportingEnabledByDeviceConfig = properties.getBoolean(key,
+ false);
+ }
+ }
+ }
+
+ private static void setupDeviceConfigProperties() {
+ if (sDeviceConfigChangeListener == null) {
+ sContentCaptureReportingEnabledByDeviceConfig = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN,
+ false);
+ sDeviceConfigChangeListener = new DeviceConfigChangeListener();
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ActivityThread.currentApplication().getMainExecutor(),
+ sDeviceConfigChangeListener);
+ }
+ }
+
public AbsListView(Context context) {
super(context);
+ setupDeviceConfigProperties();
mEdgeGlowBottom = new EdgeEffect(context);
mEdgeGlowTop = new EdgeEffect(context);
initAbsListView();
@@ -874,6 +933,7 @@
public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ setupDeviceConfigProperties();
mEdgeGlowBottom = new EdgeEffect(context, attrs);
mEdgeGlowTop = new EdgeEffect(context, attrs);
initAbsListView();
@@ -4699,6 +4759,14 @@
mOnScrollListener.onScrollStateChanged(this, newState);
}
}
+
+ // When scrolling, we want to report changes in the active children to Content Capture,
+ // so set the flag to report on the next update only when scrolling has stopped or a fling
+ // scroll is performed.
+ if (newState == OnScrollListener.SCROLL_STATE_IDLE
+ || newState == OnScrollListener.SCROLL_STATE_FLING) {
+ mReportChildrenToContentCaptureOnNextUpdate = true;
+ }
}
/**
@@ -6654,10 +6722,77 @@
mRecycler.mRecyclerListener = listener;
}
+ /**
+ * {@inheritDoc}
+ *
+ * This method will initialize the fields of the {@link ViewStructure}
+ * using the base implementation in {@link View}. On API level 33 and higher, it may also
+ * write information about the positions of active views to the extras bundle provided by the
+ * {@link ViewStructure}.
+ *
+ * NOTE: When overriding this method on API level 33, if not calling super() or if changing the
+ * logic for child views, be sure to provide values for the first active child view position and
+ * the list of active child views in the {@link ViewStructure}'s extras {@link Bundle} using the
+ * "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" and
+ * "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" keys.
+ *
+ * @param structure {@link ViewStructure} to be filled in with structured view data.
+ * @param flags optional flags.
+ *
+ * @see View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ */
+ @Override
+ public void onProvideContentCaptureStructure(
+ @NonNull ViewStructure structure, int flags) {
+ super.onProvideContentCaptureStructure(structure, flags);
+ if (!sContentCaptureReportingEnabledByDeviceConfig) {
+ return;
+ }
+
+ Bundle extras = structure.getExtras();
+
+ if (extras == null) {
+ Log.wtf(TAG, "Unexpected null extras Bundle in ViewStructure");
+ return;
+ }
+
+ int childCount = getChildCount();
+ ArrayList<AutofillId> idsList = new ArrayList<>(childCount);
+
+ for (int i = 0; i < childCount; ++i) {
+ View activeView = getChildAt(i);
+ if (activeView == null) {
+ continue;
+ }
+
+ idsList.add(activeView.getAutofillId());
+ }
+
+ extras.putParcelableArrayList(ViewStructure.EXTRA_ACTIVE_CHILDREN_IDS,
+ idsList);
+
+ extras.putInt(ViewStructure.EXTRA_FIRST_ACTIVE_POSITION,
+ getFirstVisiblePosition());
+ }
+
+ private void reportActiveViewsToContentCapture() {
+ if (!sContentCaptureReportingEnabledByDeviceConfig) {
+ return;
+ }
+
+ ContentCaptureSession session = getContentCaptureSession();
+ if (session != null) {
+ ViewStructure structure = session.newViewStructure(this);
+ onProvideContentCaptureStructure(structure, /* flags= */ 0);
+ session.notifyViewAppeared(structure);
+ }
+ }
+
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
+ mReportChildrenToContentCaptureOnNextUpdate = true;
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
@@ -6666,6 +6801,7 @@
@Override
public void onInvalidated() {
super.onInvalidated();
+ mReportChildrenToContentCaptureOnNextUpdate = true;
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
@@ -6984,6 +7120,11 @@
lp.scrappedFromPosition = firstActivePosition + i;
}
}
+
+ if (mReportChildrenToContentCaptureOnNextUpdate && childCount > 0) {
+ AbsListView.this.reportActiveViewsToContentCapture();
+ mReportChildrenToContentCaptureOnNextUpdate = false;
+ }
}
/**
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 3bffa89..e3430a6 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -269,6 +269,20 @@
}
/**
+ * Sets whether a task should be translucent. When {@code false}, the existing translucent of
+ * the task applies, but when {@code true} the task will be forced to be translucent.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setForceTranslucent(
+ @NonNull WindowContainerToken container, boolean forceTranslucent) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mForceTranslucent = forceTranslucent;
+ chg.mChangeMask |= Change.CHANGE_FORCE_TRANSLUCENT;
+ return this;
+ }
+
+ /**
* Used in conjunction with a shell-transition call (usually finishTransition). This is
* basically a message to the transition system that a particular task should NOT go into
* PIP even though it normally would. This is to deal with some edge-case situations where
@@ -834,11 +848,13 @@
public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4;
public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
+ public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
private final Configuration mConfiguration = new Configuration();
private boolean mFocusable = true;
private boolean mHidden = false;
private boolean mIgnoreOrientationRequest = false;
+ private boolean mForceTranslucent = false;
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
@@ -858,6 +874,7 @@
mFocusable = in.readBoolean();
mHidden = in.readBoolean();
mIgnoreOrientationRequest = in.readBoolean();
+ mForceTranslucent = in.readBoolean();
mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
@@ -903,6 +920,9 @@
if ((other.mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
}
+ if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
+ mForceTranslucent = other.mForceTranslucent;
+ }
mChangeMask |= other.mChangeMask;
if (other.mActivityWindowingMode >= 0) {
mActivityWindowingMode = other.mActivityWindowingMode;
@@ -953,6 +973,15 @@
return mIgnoreOrientationRequest;
}
+ /** Gets the requested force translucent state. */
+ public boolean getForceTranslucent() {
+ if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) == 0) {
+ throw new RuntimeException("Force translucent not set. "
+ + "Check CHANGE_FORCE_TRANSLUCENT first");
+ }
+ return mForceTranslucent;
+ }
+
public int getChangeMask() {
return mChangeMask;
}
@@ -1030,6 +1059,7 @@
dest.writeBoolean(mFocusable);
dest.writeBoolean(mHidden);
dest.writeBoolean(mIgnoreOrientationRequest);
+ dest.writeBoolean(mForceTranslucent);
dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 8af2450..b9243ec 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -219,8 +219,9 @@
throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
}
- HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER);
- ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE);
+ HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
+ ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
+ ParcelableColorSpace.class);
return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
colorSpace.getColorSpace());
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
new file mode 100644
index 0000000..58b8cc9
--- /dev/null
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="350dp"
+ android:layout_gravity="center"
+ android:theme="?attr/alertDialogTheme">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/fp_power_button_enrollment_title"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:paddingLeft="20dp"/>
+ <Space
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+ <Button
+ android:id="@+id/turn_off_screen"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/fp_power_button_enrollment_button_text"
+ android:paddingRight="20dp"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:maxLines="1"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d13bc87..687c642 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3519,9 +3519,9 @@
(for side fingerprint) -->
<integer name="config_sidefpsPostAuthDowntime">400</integer>
- <!-- The time (in millis) that a finger tap will wait for a power button
- before dismissing the power dialog during enrollment(for side fingerprint) -->
- <integer name="config_sidefpsEnrollPowerPressWindow">300</integer>
+ <!-- The time (in millis) the clickable toast dialog will last until
+ automatically dismissing. This is currently used in SideFpsEventHandler -->
+ <integer name="config_sideFpsToastTimeout">3000</integer>
<!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
It declares the package name of VoiceInteractionService that should be started. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a3b05b5..e3b7100 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3568,21 +3568,17 @@
<!-- [CHAR LIMIT=NONE] Message to show in upgrading dialog when the bulk of the upgrade work is done. -->
<string name="android_upgrading_complete">Finishing boot.</string>
- <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
- is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_title">Continue setup?</string>
-
<!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
button is pressed during fingerprint enrollment. -->
<string name="fp_power_button_enrollment_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint.</string>
+ <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
+ is pressed during fingerprint enrollment. -->
+ <string name="fp_power_button_enrollment_title">Tap to turn off screen</string>
+
<!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
power button is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_positive_button">Turn off screen</string>
-
- <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
- power button is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_negative_button">Continue setup</string>
+ <string name="fp_power_button_enrollment_button_text">Turn off screen</string>
<!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
is pressed during biometric prompt when a side fingerprint sensor is present. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d7484c7..1192080 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1853,8 +1853,7 @@
<java-symbol type="string" name="fp_power_button_bp_negative_button" />
<java-symbol type="string" name="fp_power_button_enrollment_title" />
<java-symbol type="string" name="fp_power_button_enrollment_message" />
- <java-symbol type="string" name="fp_power_button_enrollment_positive_button" />
- <java-symbol type="string" name="fp_power_button_enrollment_negative_button" />
+ <java-symbol type="string" name="fp_power_button_enrollment_button_text" />
<java-symbol type="string" name="global_actions" />
<java-symbol type="string" name="global_action_power_off" />
<java-symbol type="string" name="global_action_power_options" />
@@ -2631,7 +2630,11 @@
<java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/>
<java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/>
<java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/>
- <java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/>
+ <java-symbol type="integer" name="config_sideFpsToastTimeout"/>
+
+ <!-- Clickable toast used during sidefps enrollment -->
+ <java-symbol type="layout" name="side_fps_toast" />
+ <java-symbol type="id" name="turn_off_screen" />
<!-- Face authentication messages -->
<java-symbol type="string" name="face_recalibrate_notification_name" />
diff --git a/core/tests/coretests/src/android/view/WindowParamsTest.java b/core/tests/coretests/src/android/view/WindowParamsTest.java
new file mode 100644
index 0000000..49d4872
--- /dev/null
+++ b/core/tests/coretests/src/android/view/WindowParamsTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 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.view;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class WindowParamsTest {
+
+ @Test
+ public void testParamsForRotation() {
+ final WindowManager.LayoutParams paramsA = new WindowManager.LayoutParams();
+ initParamsForRotation(paramsA);
+ final Parcel parcel = Parcel.obtain();
+ paramsA.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final WindowManager.LayoutParams paramsB = new WindowManager.LayoutParams(parcel);
+ assertEquals(0, paramsA.copyFrom(paramsB));
+
+ for (int i = 1; i <= 12; i++) {
+ initParamsForRotation(paramsA);
+ changeField(i, paramsA.paramsForRotation[0]);
+ assertEquals("Change not found for case " + i,
+ WindowManager.LayoutParams.LAYOUT_CHANGED, paramsA.copyFrom(paramsB));
+ }
+
+ parcel.recycle();
+ }
+
+ private static void initParamsForRotation(WindowManager.LayoutParams params) {
+ params.paramsForRotation = new WindowManager.LayoutParams[4];
+ for (int i = 0; i < 4; i++) {
+ params.paramsForRotation[i] = new WindowManager.LayoutParams();
+ }
+ }
+
+ private static void changeField(int fieldCase, WindowManager.LayoutParams params) {
+ switch (fieldCase) {
+ case 1:
+ params.width++;
+ break;
+ case 2:
+ params.height++;
+ break;
+ case 3:
+ params.x++;
+ break;
+ case 4:
+ params.y++;
+ break;
+ case 5:
+ params.horizontalMargin++;
+ break;
+ case 6:
+ params.verticalMargin++;
+ break;
+ case 7:
+ params.layoutInDisplayCutoutMode++;
+ break;
+ case 8:
+ params.gravity++;
+ break;
+ case 9:
+ params.providedInsets = new InsetsFrameProvider[0];
+ break;
+ case 10:
+ params.setFitInsetsTypes(0);
+ break;
+ case 11:
+ params.setFitInsetsSides(0);
+ break;
+ case 12:
+ params.setFitInsetsIgnoringVisibility(!params.isFitInsetsIgnoringVisibility());
+ break;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index 312af4f..ee8c414 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -22,7 +22,6 @@
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FlingAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
-import androidx.dynamicanimation.animation.FrameCallbackScheduler
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
@@ -125,12 +124,6 @@
private var defaultFling: FlingConfig = globalDefaultFling
/**
- * FrameCallbackScheduler to use if it need custom FrameCallbackScheduler, if this is null,
- * it will use the default FrameCallbackScheduler in the DynamicAnimation.
- */
- private var customScheduler: FrameCallbackScheduler? = null
-
- /**
* Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
* the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
* just one permanent update and end listener to the DynamicAnimations.
@@ -454,14 +447,6 @@
this.defaultFling = defaultFling
}
- /**
- * Set the custom FrameCallbackScheduler for all aniatmion in this animator. Set this with null for
- * restoring to default FrameCallbackScheduler.
- */
- fun setCustomScheduler(scheduler: FrameCallbackScheduler) {
- this.customScheduler = scheduler
- }
-
/** Starts the animations! */
fun start() {
startAction()
@@ -511,12 +496,9 @@
// springs) on this property before flinging.
cancel(animatedProperty)
- // Apply the custom animation scheduler if it not null
- val flingAnim = getFlingAnimation(animatedProperty, target)
- flingAnim.scheduler = customScheduler ?: flingAnim.scheduler
-
// Apply the configuration and start the animation.
- flingAnim.also { flingConfig.applyToAnimation(it) }.start()
+ getFlingAnimation(animatedProperty, target)
+ .also { flingConfig.applyToAnimation(it) }.start()
}
}
@@ -529,18 +511,6 @@
// Apply the configuration and start the animation.
val springAnim = getSpringAnimation(animatedProperty, target)
- // If customScheduler is exist and has not been set to the animation,
- // it should set here.
- if (customScheduler != null &&
- springAnim.scheduler != customScheduler) {
- // Cancel the animation before set animation handler
- if (springAnim.isRunning) {
- cancel(animatedProperty)
- }
- // Apply the custom scheduler handler if it not null
- springAnim.scheduler = customScheduler ?: springAnim.scheduler
- }
-
// Apply the configuration and start the animation.
springConfig.applyToAnimation(springAnim)
animationStartActions.add(springAnim::start)
@@ -596,12 +566,9 @@
}
}
- // Apply the custom animation scheduler if it not null
- val springAnim = getSpringAnimation(animatedProperty, target)
- springAnim.scheduler = customScheduler ?: springAnim.scheduler
-
// Apply the configuration and start the spring animation.
- springAnim.also { springConfig.applyToAnimation(it) }.start()
+ getSpringAnimation(animatedProperty, target)
+ .also { springConfig.applyToAnimation(it) }.start()
}
}
})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index afc706e..b8204d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import android.annotation.IntDef;
@@ -55,4 +56,7 @@
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index bbaf51f..a6a04cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -128,15 +128,9 @@
mainExecutor);
}
- // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
- @BindsOptionalOf
- @DynamicOverride
- abstract DisplayImeController optionalDisplayImeController();
-
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(
- @DynamicOverride Optional<DisplayImeController> overrideDisplayImeController,
IWindowManager wmService,
ShellInit shellInit,
DisplayController displayController,
@@ -144,9 +138,6 @@
TransactionPool transactionPool,
@ShellMainThread ShellExecutor mainExecutor
) {
- if (overrideDisplayImeController.isPresent()) {
- return overrideDisplayImeController.get();
- }
return new DisplayImeController(wmService, shellInit, displayController,
displayInsetsController, transactionPool, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 81e49f8..b32c3ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -29,7 +29,6 @@
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Rect;
-import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TaskSnapshot;
@@ -279,14 +278,15 @@
mEndValue = endValue;
addListener(this);
addUpdateListener(this);
- mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mTransitionDirection = TRANSITION_DIRECTION_NONE;
}
@Override
public void onAnimationStart(Animator animation) {
mCurrentValue = mStartValue;
- onStartTransaction(mLeash, newSurfaceControlTransaction());
+ onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
}
@@ -294,14 +294,16 @@
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
+ applySurfaceControlTransaction(mLeash,
+ mSurfaceControlTransactionFactory.getTransaction(),
animation.getAnimatedFraction());
}
@Override
public void onAnimationEnd(Animator animation) {
mCurrentValue = mEndValue;
- final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
onEndTransaction(mLeash, tx, mTransitionDirection);
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
@@ -348,7 +350,8 @@
}
void setColorContentOverlay(Context context) {
- final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
@@ -357,7 +360,8 @@
}
void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
- final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
@@ -406,7 +410,7 @@
void setDestinationBounds(Rect destinationBounds) {
mDestinationBounds.set(destinationBounds);
if (mAnimationType == ANIM_TYPE_ALPHA) {
- onStartTransaction(mLeash, newSurfaceControlTransaction());
+ onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
}
}
@@ -441,16 +445,6 @@
mEndValue = endValue;
}
- /**
- * @return {@link SurfaceControl.Transaction} instance with vsync-id.
- */
- protected SurfaceControl.Transaction newSurfaceControlTransaction() {
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- return tx;
- }
-
@VisibleForTesting
public void setSurfaceControlTransactionFactory(
PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3ac08a6..b9746e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -20,6 +20,7 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.view.Choreographer;
import android.view.SurfaceControl;
import com.android.wm.shell.R;
@@ -234,4 +235,18 @@
public interface SurfaceControlTransactionFactory {
SurfaceControl.Transaction getTransaction();
}
+
+ /**
+ * Implementation of {@link SurfaceControlTransactionFactory} that returns
+ * {@link SurfaceControl.Transaction} with VsyncId being set.
+ */
+ public static class VsyncSurfaceControlTransactionFactory
+ implements SurfaceControlTransactionFactory {
+ @Override
+ public SurfaceControl.Transaction getTransaction() {
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ return tx;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f747b5e..b46eff6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -304,7 +304,8 @@
mSurfaceTransactionHelper = surfaceTransactionHelper;
mPipAnimationController = pipAnimationController;
mPipUiEventLoggerLogger = pipUiEventLogger;
- mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 5a21e07..44d2202 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -33,10 +33,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
-import android.os.Looper;
-import android.view.Choreographer;
-
-import androidx.dynamicanimation.animation.FrameCallbackScheduler;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
@@ -89,25 +85,6 @@
/** Coordinator instance for resolving conflicts with other floating content. */
private FloatingContentCoordinator mFloatingContentCoordinator;
- private ThreadLocal<FrameCallbackScheduler> mSfSchedulerThreadLocal =
- ThreadLocal.withInitial(() -> {
- final Looper initialLooper = Looper.myLooper();
- final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() {
- @Override
- public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) {
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for
- // transactions
- Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
- }
-
- @Override
- public boolean isCurrentThread() {
- return Looper.myLooper() == initialLooper;
- }
- };
- return scheduler;
- });
-
/**
* PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
* using physics animations.
@@ -210,10 +187,8 @@
}
public void init() {
- // Note: Needs to get the shell main thread sf vsync animation handler
mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
- mTemporaryBoundsPhysicsAnimator.setCustomScheduler(mSfSchedulerThreadLocal.get());
}
@NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 8e1ae39..4bc8e91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -32,13 +32,13 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -147,9 +147,6 @@
private static final String TAG = StageCoordinator.class.getSimpleName();
- /** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
-
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final MainStage mMainStage;
@@ -894,6 +891,7 @@
}
});
mShouldUpdateRecents = false;
+ mIsDividerRemoteAnimating = false;
if (childrenToTop == null) {
mSideStage.removeAllTasks(wct, false /* toTop */);
@@ -1808,7 +1806,8 @@
boolean shouldAnimate = true;
if (mSplitTransitions.isPendingEnter(transition)) {
- shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
+ shouldAnimate = startPendingEnterAnimation(
+ transition, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingRecent(transition)) {
shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
@@ -1836,7 +1835,8 @@
}
private boolean startPendingEnterAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction finishT) {
// First, verify that we actually have opened apps in both splits.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
@@ -1883,8 +1883,8 @@
+ " before startAnimation().");
}
- finishEnterSplitScreen(t);
- addDividerBarToTransition(info, t, true /* show */);
+ finishEnterSplitScreen(finishT);
+ addDividerBarToTransition(info, finishT, true /* show */);
return true;
}
@@ -1969,7 +1969,7 @@
return false;
}
- addDividerBarToTransition(info, t, false /* show */);
+ addDividerBarToTransition(info, finishT, false /* show */);
return true;
}
@@ -1980,23 +1980,25 @@
}
private void addDividerBarToTransition(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, boolean show) {
+ @NonNull SurfaceControl.Transaction finishT, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
- final Rect bounds = mSplitLayout.getDividerBounds();
- barChange.setStartAbsBounds(bounds);
- barChange.setEndAbsBounds(bounds);
+ mSplitLayout.getRefDividerBounds(mTempRect1);
+ barChange.setStartAbsBounds(mTempRect1);
+ barChange.setEndAbsBounds(mTempRect1);
barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
barChange.setFlags(FLAG_IS_DIVIDER_BAR);
// Technically this should be order-0, but this is running after layer assignment
// and it's a special case, so just add to end.
info.addChange(barChange);
- // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
+
if (show) {
- t.setAlpha(leash, 1.f);
- t.setLayer(leash, Integer.MAX_VALUE);
- t.setPosition(leash, bounds.left, bounds.top);
- t.show(leash);
+ finishT.setLayer(leash, Integer.MAX_VALUE);
+ finishT.setPosition(leash, mTempRect1.left, mTempRect1.top);
+ finishT.show(leash);
+ // Ensure divider surface are re-parented back into the hierarchy at the end of the
+ // transition. See Transition#buildFinishTransaction for more detail.
+ finishT.reparent(leash, mRootTaskLeash);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 5cce6b9..e26c259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -20,9 +20,9 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
-import static com.android.wm.shell.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index a843b2a..45b69f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -162,13 +162,12 @@
.setParent(mAnimLeash)
.setBLASTLayer()
.setSecure(screenshotBuffer.containsSecureLayers())
+ .setOpaque(true)
.setCallsite("ShellRotationAnimation")
.setName("RotationLayer")
.build();
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
- t.setPosition(mAnimLeash, 0, 0);
- t.setAlpha(mAnimLeash, 1);
t.show(mAnimLeash);
final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
@@ -181,6 +180,7 @@
mBackColorSurface = new SurfaceControl.Builder(session)
.setParent(rootLeash)
.setColorLayer()
+ .setOpaque(true)
.setCallsite("ShellRotationAnimation")
.setName("BackColorSurface")
.build();
@@ -189,7 +189,6 @@
t.setLayer(mBackColorSurface, -1);
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
- t.setAlpha(mBackColorSurface, 1);
t.show(mBackColorSurface);
}
@@ -242,7 +241,6 @@
t.setMatrix(mScreenshotLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
- t.setAlpha(mScreenshotLayer, (float) 1.0);
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
index 514390f..d467b39 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
@@ -47,6 +47,7 @@
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
/**
* Tests for {@link DisplayLayout}.
@@ -62,6 +63,7 @@
public void setup() {
mMockitoSession = mockitoSession()
.initMocks(this)
+ .strictness(Strictness.WARN)
.mockStatic(SystemBarUtils.class)
.startMocking();
}
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 70a7709..c972624 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -35,10 +35,20 @@
app:layout_constraintBottom_toBottomOf="parent" />
<LinearLayout
+ android:id="@+id/dream_overlay_extra_items"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginEnd="@dimen/dream_overlay_status_bar_extra_margin"
+ app:layout_constraintEnd_toStartOf="@+id/dream_overlay_system_status" />
+
+ <LinearLayout
android:id="@+id/dream_overlay_system_status"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
+ android:layout_marginStart="@dimen/dream_overlay_status_bar_extra_margin"
app:layout_constraintEnd_toEndOf="parent">
<com.android.systemui.statusbar.AlphaOptimizedImageView
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3fb00a3..8ea2c0c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1455,6 +1455,7 @@
<dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>
<dimen name="dream_overlay_notification_indicator_size">6dp</dimen>
<dimen name="dream_overlay_grey_chip_width">56dp</dimen>
+ <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen>
<!-- Dream overlay complications related dimensions -->
<dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 203b236..7e42e1b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -170,7 +170,7 @@
/** @return {@link SurfaceControl.Transaction} instance with vsync-id */
public static SurfaceControl.Transaction newSurfaceControlTransaction() {
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+ tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
return tx;
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 9265f07..33e8e35 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -122,12 +122,13 @@
IRemoteTransitionFinishedCallback finishCallback) {
final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
final RemoteAnimationTargetCompat[] appsCompat =
- RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap);
+ RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
final RemoteAnimationTargetCompat[] wallpapersCompat =
- RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap);
- // TODO(bc-unlock): Build wrapped object for non-apps target.
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, true /* wallpapers */, t, leashMap);
final RemoteAnimationTargetCompat[] nonAppsCompat =
- new RemoteAnimationTargetCompat[0];
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, false /* wallpapers */, t, leashMap);
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
boolean isReturnToHome = false;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index ef9e095..7c3b5fc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -16,7 +16,9 @@
package com.android.systemui.shared.system;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -24,6 +26,8 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -76,7 +80,7 @@
private final SurfaceControl mStartLeash;
- // Fields used only to unrap into RemoteAnimationTarget
+ // Fields used only to unwrap into RemoteAnimationTarget
private final Rect startBounds;
public final boolean willShowImeOnTarget;
@@ -203,8 +207,19 @@
public RemoteAnimationTargetCompat(TransitionInfo.Change change, int order,
TransitionInfo info, SurfaceControl.Transaction t) {
- taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;
mode = newModeToLegacyMode(change.getMode());
+ taskInfo = change.getTaskInfo();
+ if (taskInfo != null) {
+ taskId = taskInfo.taskId;
+ isNotInRecents = !taskInfo.isRunning;
+ activityType = taskInfo.getActivityType();
+ windowConfiguration = taskInfo.configuration.windowConfiguration;
+ } else {
+ taskId = INVALID_TASK_ID;
+ isNotInRecents = true;
+ activityType = ACTIVITY_TYPE_UNDEFINED;
+ windowConfiguration = new WindowConfiguration();
+ }
// TODO: once we can properly sync transactions across process, then get rid of this leash.
leash = createLeash(info, change, order, t);
@@ -221,22 +236,12 @@
prefixOrderIndex = order;
// TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.
contentInsets = new Rect(0, 0, 0, 0);
- if (change.getTaskInfo() != null) {
- isNotInRecents = !change.getTaskInfo().isRunning;
- activityType = change.getTaskInfo().getActivityType();
- } else {
- isNotInRecents = true;
- activityType = ACTIVITY_TYPE_UNDEFINED;
- }
- taskInfo = change.getTaskInfo();
allowEnterPip = change.getAllowEnterPip();
mStartLeash = null;
rotationChange = change.getEndRotation() - change.getStartRotation();
- windowType = INVALID_WINDOW_TYPE;
+ windowType = (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0
+ ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE;
- windowConfiguration = change.getTaskInfo() != null
- ? change.getTaskInfo().configuration.windowConfiguration
- : new WindowConfiguration();
startBounds = change.getStartAbsBounds();
willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
}
@@ -251,37 +256,62 @@
}
/**
- * Represents a TransitionInfo object as an array of old-style targets
+ * Represents a TransitionInfo object as an array of old-style app targets
+ *
+ * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
+ * populated by this function. If null, it is ignored.
+ */
+ public static RemoteAnimationTargetCompat[] wrapApps(TransitionInfo info,
+ SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+ final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
+ final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>();
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() == null) continue;
+
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ // Children always come before parent since changes are in top-to-bottom z-order.
+ if (taskInfo != null) {
+ if (childTaskTargets.contains(taskInfo.taskId)) {
+ // has children, so not a leaf. Skip.
+ continue;
+ }
+ if (taskInfo.hasParentTask()) {
+ childTaskTargets.put(taskInfo.parentTaskId, change);
+ }
+ }
+
+ final RemoteAnimationTargetCompat targetCompat =
+ new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
+ if (leashMap != null) {
+ leashMap.put(change.getLeash(), targetCompat.leash);
+ }
+ out.add(targetCompat);
+ }
+
+ return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
+ }
+
+ /**
+ * Represents a TransitionInfo object as an array of old-style non-app targets
*
* @param wallpapers If true, this will return wallpaper targets; otherwise it returns
* non-wallpaper targets.
* @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
* populated by this function. If null, it is ignored.
*/
- public static RemoteAnimationTargetCompat[] wrap(TransitionInfo info, boolean wallpapers,
+ public static RemoteAnimationTargetCompat[] wrapNonApps(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
- final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>();
+
for (int i = 0; i < info.getChanges().size(); i++) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null) continue;
+
final boolean changeIsWallpaper =
(change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
if (wallpapers != changeIsWallpaper) continue;
- if (!wallpapers) {
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- // Children always come before parent since changes are in top-to-bottom z-order.
- if (taskInfo != null) {
- if (childTaskTargets.contains(taskInfo.taskId)) {
- // has children, so not a leaf. Skip.
- continue;
- }
- if (taskInfo.hasParentTask()) {
- childTaskTargets.put(taskInfo.parentTaskId, change);
- }
- }
- }
-
final RemoteAnimationTargetCompat targetCompat =
new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
if (leashMap != null) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 7c1ef8c..f679225 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -128,9 +128,10 @@
IRemoteTransitionFinishedCallback finishedCallback) {
final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
final RemoteAnimationTargetCompat[] apps =
- RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap);
+ RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
final RemoteAnimationTargetCompat[] wallpapers =
- RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap);
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, true /* wallpapers */, t, leashMap);
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
mToken = transition;
// This transition is for opening recents, so recents is on-top. We want to draw
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 78a45f9..b6923a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -75,25 +75,12 @@
* Initializes all the WMShell components before starting any of the SystemUI components.
*/
default void init() {
- // TODO(238217847): To be removed once the dependencies are inverted and ShellController can
- // inject these classes directly, otherwise, it's currently needed to ensure that these
- // classes are created and set on the controller before onInit() is called
- getShellInit();
- getShellCommandHandler();
getShell().onInit();
}
@WMSingleton
ShellInterface getShell();
- // TODO(238217847): To be removed once ShellController can inject ShellInit directly
- @WMSingleton
- ShellInit getShellInit();
-
- // TODO(238217847): To be removed once ShellController can inject ShellCommandHandler directly
- @WMSingleton
- ShellCommandHandler getShellCommandHandler();
-
@WMSingleton
Optional<OneHanded> getOneHanded();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java
new file mode 100644
index 0000000..193d6f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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.dreams;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * {@link DreamOverlayStatusBarItemsProvider} provides extra dream overlay status bar items. A
+ * callback can be registered that will be informed of items being added or removed from the
+ * provider.
+ */
+@SysUISingleton
+public class DreamOverlayStatusBarItemsProvider implements
+ CallbackController<DreamOverlayStatusBarItemsProvider.Callback> {
+ /**
+ * Represents one item in the dream overlay status bar.
+ */
+ public interface StatusBarItem {
+ /**
+ * Return the {@link View} associated with this item.
+ */
+ View getView();
+ }
+
+ /**
+ * A callback to be registered with the provider to be informed of when the list of status bar
+ * items has changed.
+ */
+ public interface Callback {
+ /**
+ * Inform the callback that status bar items have changed.
+ */
+ void onStatusBarItemsChanged(List<StatusBarItem> newItems);
+ }
+
+ private final Executor mExecutor;
+ private final List<StatusBarItem> mItems = new ArrayList<>();
+ private final List<Callback> mCallbacks = new ArrayList<>();
+
+ @Inject
+ public DreamOverlayStatusBarItemsProvider(@Main Executor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void addCallback(@NonNull Callback callback) {
+ mExecutor.execute(() -> {
+ Objects.requireNonNull(callback, "Callback must not be null.");
+ if (mCallbacks.contains(callback)) {
+ return;
+ }
+
+ mCallbacks.add(callback);
+ if (!mItems.isEmpty()) {
+ callback.onStatusBarItemsChanged(mItems);
+ }
+ });
+ }
+
+ @Override
+ public void removeCallback(@NonNull Callback callback) {
+ mExecutor.execute(() -> {
+ Objects.requireNonNull(callback, "Callback must not be null.");
+ mCallbacks.remove(callback);
+ });
+ }
+
+ /**
+ * Adds an item to the dream overlay status bar.
+ */
+ public void addStatusBarItem(StatusBarItem item) {
+ mExecutor.execute(() -> {
+ if (!mItems.contains(item)) {
+ mItems.add(item);
+ mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems));
+ }
+ });
+ }
+
+ /**
+ * Removes an item from the dream overlay status bar.
+ */
+ public void removeStatusBarItem(StatusBarItem item) {
+ mExecutor.execute(() -> {
+ if (mItems.remove(item)) {
+ mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems));
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index a25257d..7e4a108 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -29,6 +30,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -58,6 +60,7 @@
public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
private final Map<Integer, View> mStatusIcons = new HashMap<>();
+ private ViewGroup mSystemStatusViewGroup;
public DreamOverlayStatusBarView(Context context) {
this(context, null);
@@ -94,6 +97,8 @@
fetchStatusIconForResId(R.id.dream_overlay_notification_indicator));
mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON,
fetchStatusIconForResId(R.id.dream_overlay_priority_mode));
+
+ mSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
}
void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
@@ -107,6 +112,11 @@
icon.setVisibility(show ? View.VISIBLE : View.GONE);
}
+ void setExtraStatusBarItemViews(List<View> views) {
+ mSystemStatusViewGroup.removeAllViews();
+ views.forEach(view -> mSystemStatusViewGroup.addView(view));
+ }
+
private View fetchStatusIconForResId(int resId) {
final View statusIcon = findViewById(resId);
return Objects.requireNonNull(statusIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 55c1806..65cfae1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -38,6 +38,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -47,10 +48,13 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.time.DateFormatUtil;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -69,7 +73,10 @@
private final Optional<DreamOverlayNotificationCountProvider>
mDreamOverlayNotificationCountProvider;
private final ZenModeController mZenModeController;
+ private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
private final Executor mMainExecutor;
+ private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
+ new ArrayList<>();
private boolean mIsAttached;
@@ -116,6 +123,9 @@
? buildNotificationsContentDescription(notificationCount)
: null);
+ private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback =
+ this::onStatusBarItemsChanged;
+
@Inject
public DreamOverlayStatusBarViewController(
DreamOverlayStatusBarView view,
@@ -129,7 +139,8 @@
IndividualSensorPrivacyController sensorPrivacyController,
Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider,
ZenModeController zenModeController,
- StatusBarWindowStateController statusBarWindowStateController) {
+ StatusBarWindowStateController statusBarWindowStateController,
+ DreamOverlayStatusBarItemsProvider statusBarItemsProvider) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -140,6 +151,7 @@
mDateFormatUtil = dateFormatUtil;
mSensorPrivacyController = sensorPrivacyController;
mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider;
+ mStatusBarItemsProvider = statusBarItemsProvider;
mZenModeController = zenModeController;
// Register to receive show/hide updates for the system status bar. Our custom status bar
@@ -166,6 +178,8 @@
mDreamOverlayNotificationCountProvider.ifPresent(
provider -> provider.addCallback(mNotificationCountCallback));
+ mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
+
mTouchInsetSession.addViewToTracking(mView);
}
@@ -177,6 +191,7 @@
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mDreamOverlayNotificationCountProvider.ifPresent(
provider -> provider.removeCallback(mNotificationCountCallback));
+ mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
mTouchInsetSession.clear();
mIsAttached = false;
@@ -271,4 +286,16 @@
}
});
}
+
+ private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
+ mMainExecutor.execute(() -> {
+ mExtraStatusBarItems.clear();
+ mExtraStatusBarItems.addAll(newItems);
+ mView.setExtraStatusBarItemViews(
+ newItems
+ .stream()
+ .map(StatusBarItem::getView)
+ .collect(Collectors.toList()));
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index d701f33..c790cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs
import android.content.Intent
+import android.content.res.Configuration
import android.os.Handler
import android.os.UserManager
import android.provider.Settings
@@ -38,9 +39,11 @@
import com.android.systemui.qs.dagger.QSScope
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
+import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -69,18 +72,43 @@
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
private val globalSetting: GlobalSettings,
- private val handler: Handler
+ private val handler: Handler,
+ private val configurationController: ConfigurationController,
) : ViewController<FooterActionsView>(view) {
private var globalActionsDialog: GlobalActionsDialogLite? = null
private var lastExpansion = -1f
private var listening: Boolean = false
+ private var inSplitShade = false
- private val alphaAnimator = TouchAnimator.Builder()
- .addFloat(mView, "alpha", 0f, 1f)
- .setStartDelay(0.9f)
+ private val singleShadeAnimator by lazy {
+ // In single shade, the actions footer should only appear at the end of the expansion,
+ // so that it doesn't overlap with the notifications panel.
+ TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build()
+ }
+
+ private val splitShadeAnimator by lazy {
+ // The Actions footer view has its own background which is the same color as the qs panel's
+ // background.
+ // We don't want it to fade in at the same time as the rest of the panel, otherwise it is
+ // more opaque than the rest of the panel's background. Only applies to split shade.
+ val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build()
+ val bgAlphaAnimator =
+ TouchAnimator.Builder()
+ .addFloat(mView, "backgroundAlpha", 0f, 1f)
+ .setStartDelay(0.9f)
+ .build()
+ // In split shade, we want the actions footer to fade in exactly at the same time as the
+ // rest of the shade, as there is no overlap.
+ TouchAnimator.Builder()
+ .addFloat(alphaAnimator, "position", 0f, 1f)
+ .addFloat(bgAlphaAnimator, "position", 0f, 1f)
.build()
+ }
+
+ private val animators: TouchAnimator
+ get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator
var visible = true
set(value) {
@@ -95,9 +123,7 @@
private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
@VisibleForTesting
- internal val securityFootersSeparator = View(context).apply {
- visibility = View.GONE
- }
+ internal val securityFootersSeparator = View(context).apply { visibility = View.GONE }
private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -133,6 +159,17 @@
}
}
+ private val configurationListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ }
+
+ private fun updateResources() {
+ inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
+ }
+
override fun onInit() {
multiUserSwitchController.init()
securityFooterController.init()
@@ -189,6 +226,9 @@
securityFooterController.setOnVisibilityChangedListener(visibilityListener)
fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
+ configurationController.addCallback(configurationListener)
+
+ updateResources()
updateView()
}
@@ -201,6 +241,7 @@
globalActionsDialog = null
setListening(false)
multiUserSetting.isListening = false
+ configurationController.removeCallback(configurationListener)
}
fun setListening(listening: Boolean) {
@@ -224,7 +265,7 @@
}
fun setExpansion(headerExpansionFraction: Float) {
- alphaAnimator.setPosition(headerExpansionFraction)
+ animators.setPosition(headerExpansionFraction)
}
fun setKeyguardShowing(showing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index 05038b7..309ac2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -27,6 +27,7 @@
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
+import androidx.annotation.Keep
import com.android.settingslib.Utils
import com.android.settingslib.drawable.UserIconDrawable
import com.android.systemui.R
@@ -45,6 +46,19 @@
private var qsDisabled = false
private var expansionAmount = 0f
+ /**
+ * Sets the alpha of the background of this view.
+ *
+ * Used from a [TouchAnimator] in the controller.
+ */
+ var backgroundAlpha: Float = 1f
+ @Keep
+ set(value) {
+ field = value
+ background?.alpha = (value * 255).toInt()
+ }
+ @Keep get
+
override fun onFinishInflate() {
super.onFinishInflate()
settingsContainer = findViewById(R.id.settings_button_container)
@@ -117,4 +131,4 @@
private const val TAG = "FooterActionsView"
private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE)
private val MotionEvent.string
- get() = "($id): ($x,$y)"
\ No newline at end of file
+ get() = "($id): ($x,$y)"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 8c5e6cc..139fb8b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -557,9 +557,9 @@
public void setQsExpansion(float expansion, float panelExpansionFraction,
float proposedTranslation, float squishinessFraction) {
float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
- float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
+ float alphaProgress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
? mFullShadeProgress : panelExpansionFraction;
- setAlphaAnimationProgress(mInSplitShade ? progress : 1);
+ setAlphaAnimationProgress(mInSplitShade ? alphaProgress : 1);
mContainer.setExpansion(expansion);
final float translationScaleY = (mInSplitShade
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
@@ -600,7 +600,9 @@
}
mQSPanelController.setIsOnKeyguard(onKeyguard);
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
- mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
+ float footerActionsExpansion =
+ onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
+ mQSFooterActionController.setExpansion(footerActionsExpansion);
mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 324c019..7155626 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -128,6 +128,8 @@
if (mUsingMediaPlayer) {
mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mHorizontalLinearLayout.setVisibility(
+ mUsingHorizontalLayout ? View.VISIBLE : View.GONE);
mHorizontalLinearLayout.setClipChildren(false);
mHorizontalLinearLayout.setClipToPadding(false);
@@ -445,6 +447,8 @@
mMediaHostView = hostView;
ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
ViewGroup currentParent = (ViewGroup) hostView.getParent();
+ Log.d(getDumpableTag(), "Reattaching media host: " + horizontal
+ + ", current " + currentParent + ", new " + newParent);
if (currentParent != newParent) {
if (currentParent != null) {
currentParent.removeView(hostView);
@@ -589,6 +593,7 @@
void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force) {
if (horizontal != mUsingHorizontalLayout || force) {
+ Log.d(getDumpableTag(), "setUsingHorizontalLayout: " + horizontal + ", " + force);
mUsingHorizontalLayout = horizontal;
ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
switchAllContentToParent(newParent, mTileLayout);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ec61ea6..6d5f844 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -88,6 +88,8 @@
public void onConfigurationChange(Configuration newConfig) {
mShouldUseSplitNotificationShade =
LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+ mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
+ mView.getDumpableTag());
onConfigurationChanged();
if (newConfig.orientation != mLastOrientation) {
mLastOrientation = newConfig.orientation;
@@ -164,6 +166,7 @@
mHost.addCallback(mQSHostCallback);
setTiles();
mLastOrientation = getResources().getConfiguration().orientation;
+ mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag());
switchTileLayout(true);
mDumpManager.registerDumpable(mView.getDumpableTag(), this);
@@ -171,6 +174,7 @@
@Override
protected void onViewDetached() {
+ mQSLogger.logOnViewDetached(mLastOrientation, mView.getDumpableTag());
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.removeCallback(mQSHostCallback);
@@ -325,6 +329,8 @@
/* Whether or not the panel currently contains a media player. */
boolean horizontal = shouldUseHorizontalLayout();
if (horizontal != mUsingHorizontalLayout || force) {
+ mQSLogger.logSwitchTileLayout(horizontal, mUsingHorizontalLayout, force,
+ mView.getDumpableTag());
mUsingHorizontalLayout = horizontal;
mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force);
updateMediaDisappearParameters();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index ab795fa..948fb14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -155,6 +155,54 @@
})
}
+ fun logOnViewAttached(orientation: Int, containerName: String) {
+ log(DEBUG, {
+ str1 = containerName
+ int1 = orientation
+ }, {
+ "onViewAttached: $str1 orientation $int1"
+ })
+ }
+
+ fun logOnViewDetached(orientation: Int, containerName: String) {
+ log(DEBUG, {
+ str1 = containerName
+ int1 = orientation
+ }, {
+ "onViewDetached: $str1 orientation $int1"
+ })
+ }
+
+ fun logOnConfigurationChanged(
+ lastOrientation: Int,
+ newOrientation: Int,
+ containerName: String
+ ) {
+ log(DEBUG, {
+ str1 = containerName
+ int1 = lastOrientation
+ int2 = newOrientation
+ }, {
+ "configuration change: $str1 orientation was $int1, now $int2"
+ })
+ }
+
+ fun logSwitchTileLayout(
+ after: Boolean,
+ before: Boolean,
+ force: Boolean,
+ containerName: String
+ ) {
+ log(DEBUG, {
+ str1 = containerName
+ bool1 = after
+ bool2 = before
+ bool3 = force
+ }, {
+ "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3"
+ })
+ }
+
private fun toStateString(state: Int): String {
return when (state) {
Tile.STATE_ACTIVE -> "active"
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 13a5615..2a46776 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -6,7 +6,11 @@
import android.view.WindowInsets
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.*
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
@@ -171,33 +175,23 @@
private fun calculateBottomSpacing(): Paddings {
val containerPadding: Int
- var stackScrollMargin = notificationsBottomMargin
- if (splitShadeEnabled) {
- if (isGestureNavigation) {
- // only default cutout padding, taskbar always hides
- containerPadding = bottomCutoutInsets
- } else if (taskbarVisible) {
- // navigation buttons + visible taskbar means we're NOT on homescreen
- containerPadding = bottomStableInsets
- } else {
- // navigation buttons + hidden taskbar means we're on homescreen
- containerPadding = 0
- // we need extra margin for notifications as navigation buttons are below them
- stackScrollMargin = bottomStableInsets + notificationsBottomMargin
- }
+ val stackScrollMargin: Int
+ if (!splitShadeEnabled && (isQSCustomizing || isQSDetailShowing)) {
+ // Clear out bottom paddings/margins so the qs customization can be full height.
+ containerPadding = 0
+ stackScrollMargin = 0
+ } else if (isGestureNavigation) {
+ // only default cutout padding, taskbar always hides
+ containerPadding = bottomCutoutInsets
+ stackScrollMargin = notificationsBottomMargin
+ } else if (taskbarVisible) {
+ // navigation buttons + visible taskbar means we're NOT on homescreen
+ containerPadding = bottomStableInsets
+ stackScrollMargin = notificationsBottomMargin
} else {
- if (isQSCustomizing || isQSDetailShowing) {
- // Clear out bottom paddings/margins so the qs customization can be full height.
- containerPadding = 0
- stackScrollMargin = 0
- } else if (isGestureNavigation) {
- containerPadding = bottomCutoutInsets
- } else if (taskbarVisible) {
- containerPadding = bottomStableInsets
- } else {
- containerPadding = 0
- stackScrollMargin = bottomStableInsets + notificationsBottomMargin
- }
+ // navigation buttons + hidden taskbar means we're on homescreen
+ containerPadding = 0
+ stackScrollMargin = bottomStableInsets + notificationsBottomMargin
}
val qsContainerPadding = if (!(isQSCustomizing || isQSDetailShowing)) {
// We also want this padding in the bottom in these cases
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 420f21d..14cc6bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -38,6 +38,7 @@
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -126,6 +127,9 @@
private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList);
private final NotifPipelineChoreographer mChoreographer;
+ private int mConsecutiveReentrantRebuilds = 0;
+ @VisibleForTesting public static final int MAX_CONSECUTIVE_REENTRANT_REBUILDS = 3;
+
@Inject
public ShadeListBuilder(
DumpManager dumpManager,
@@ -310,7 +314,7 @@
mLogger.logOnBuildList(reason);
mAllEntries = entries;
- mChoreographer.schedule();
+ scheduleRebuild(/* reentrant = */ false);
}
};
@@ -1332,11 +1336,64 @@
throw new RuntimeException("Missing default sectioner!");
}
- private void rebuildListIfBefore(@PipelineState.StateName int state) {
- mPipelineState.requireIsBefore(state);
- if (mPipelineState.is(STATE_IDLE)) {
- mChoreographer.schedule();
+ private void rebuildListIfBefore(@PipelineState.StateName int rebuildState) {
+ final @PipelineState.StateName int currentState = mPipelineState.getState();
+
+ // If the pipeline is idle, requesting an invalidation is always okay, and starts a new run.
+ if (currentState == STATE_IDLE) {
+ scheduleRebuild(/* reentrant = */ false, rebuildState);
+ return;
}
+
+ // If the pipeline is running, it is okay to request an invalidation of a *later* stage.
+ // Since the current pipeline run hasn't run it yet, no new pipeline run is needed.
+ if (rebuildState > currentState) {
+ return;
+ }
+
+ // If the pipeline is running, it is bad to request an invalidation of *earlier* stages or
+ // the *current* stage; this will run the pipeline more often than needed, and may even
+ // cause an infinite loop of pipeline runs.
+ //
+ // Unfortunately, there are some unfixed bugs that cause reentrant pipeline runs, so we keep
+ // a counter and allow a few reentrant runs in a row between any two non-reentrant runs.
+ //
+ // It is technically possible for a *pair* of invalidations, one reentrant and one not, to
+ // trigger *each other*, alternating responsibility for pipeline runs in an infinite loop
+ // but constantly resetting the reentrant run counter. Hopefully that doesn't happen.
+ scheduleRebuild(/* reentrant = */ true, rebuildState);
+ }
+
+ private void scheduleRebuild(boolean reentrant) {
+ scheduleRebuild(reentrant, STATE_IDLE);
+ }
+
+ private void scheduleRebuild(boolean reentrant, @PipelineState.StateName int rebuildState) {
+ if (!reentrant) {
+ mConsecutiveReentrantRebuilds = 0;
+ mChoreographer.schedule();
+ return;
+ }
+
+ final @PipelineState.StateName int currentState = mPipelineState.getState();
+
+ final String rebuildStateName = PipelineState.getStateName(rebuildState);
+ final String currentStateName = PipelineState.getStateName(currentState);
+ final IllegalStateException exception = new IllegalStateException(
+ "Reentrant notification pipeline rebuild of state " + rebuildStateName
+ + " while pipeline in state " + currentStateName + ".");
+
+ mConsecutiveReentrantRebuilds++;
+
+ if (mConsecutiveReentrantRebuilds > MAX_CONSECUTIVE_REENTRANT_REBUILDS) {
+ Log.e(TAG, "Crashing after more than " + MAX_CONSECUTIVE_REENTRANT_REBUILDS
+ + " consecutive reentrant notification pipeline rebuilds.", exception);
+ throw exception;
+ }
+
+ Log.e(TAG, "Allowing " + mConsecutiveReentrantRebuilds
+ + " consecutive reentrant notification pipeline rebuild(s).", exception);
+ mChoreographer.schedule();
}
private static int countChildren(List<ListEntry> entries) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ef1e57b..6e76691 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -286,7 +286,7 @@
if (isInflated(child)) {
// TODO: May want to put an animation hint here so view manager knows to treat
// this differently from a regular removal animation
- freeNotifViews(child);
+ freeNotifViews(child, "Past last visible group child");
}
}
}
@@ -379,7 +379,8 @@
mNotifInflatingFilter.invalidateList("onInflationFinished for " + logKey(entry));
}
- private void freeNotifViews(NotificationEntry entry) {
+ private void freeNotifViews(NotificationEntry entry, String reason) {
+ mLogger.logFreeNotifViews(entry, reason);
mViewBarn.removeViewForEntry(entry);
mNotifInflater.releaseViews(entry);
// TODO: clear the entry's row here, or even better, stop setting the row on the entry!
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 30f1315..c4f4ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -31,7 +31,7 @@
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry.logKey
}, {
- "NOTIF INFLATED $str1"
+ "Inflation completed for notif $str1"
})
}
@@ -40,7 +40,16 @@
str1 = entry.logKey
str2 = reason
}, {
- "NOTIF INFLATION ABORTED $str1 reason=$str2"
+ "Infation aborted for notif $str1 reason=$str2"
+ })
+ }
+
+ fun logFreeNotifViews(entry: NotificationEntry, reason: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.logKey
+ str2 = reason
+ }, {
+ "Freeing content views for notif $str1 reason=$str2"
})
}
@@ -70,4 +79,4 @@
}
}
-private const val TAG = "PreparationCoordinator"
\ No newline at end of file
+private const val TAG = "PreparationCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
index 96b9aca..2763750 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -17,11 +17,14 @@
package com.android.systemui.statusbar.phone
import android.annotation.ColorInt
+import android.app.WallpaperManager
import android.graphics.Color
+import android.os.Handler
import android.os.RemoteException
import android.view.IWindowManager
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
@@ -37,6 +40,8 @@
private val windowManager: IWindowManager,
@Background private val backgroundExecutor: Executor,
private val dumpManager: DumpManager,
+ private val wallpaperManager: WallpaperManager,
+ @Main private val mainHandler: Handler,
) : CentralSurfacesComponent.Startable, Dumpable {
@ColorInt
@@ -46,9 +51,18 @@
var isLetterboxBackgroundMultiColored: Boolean = false
private set
+ private val wallpaperColorsListener =
+ WallpaperManager.OnColorsChangedListener { _, _ ->
+ fetchBackgroundColorInfo()
+ }
+
override fun start() {
dumpManager.registerDumpable(javaClass.simpleName, this)
+ fetchBackgroundColorInfo()
+ wallpaperManager.addOnColorsChangedListener(wallpaperColorsListener, mainHandler)
+ }
+ private fun fetchBackgroundColorInfo() {
// Using a background executor, as binder calls to IWindowManager are blocking
backgroundExecutor.execute {
try {
@@ -62,6 +76,7 @@
override fun stop() {
dumpManager.unregisterDumpable(javaClass.simpleName)
+ wallpaperManager.removeOnColorsChangedListener(wallpaperColorsListener)
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
new file mode 100644
index 0000000..a78886f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase {
+ @Mock
+ DreamOverlayStatusBarItemsProvider.Callback mCallback;
+ @Mock
+ DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem;
+
+ private final Executor mMainExecutor = Runnable::run;
+
+ DreamOverlayStatusBarItemsProvider mProvider;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mProvider = new DreamOverlayStatusBarItemsProvider(mMainExecutor);
+ }
+
+ @Test
+ public void addingCallbackCallsOnStatusBarItemsChanged() {
+ mProvider.addStatusBarItem(mStatusBarItem);
+ mProvider.addCallback(mCallback);
+ verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem));
+ }
+
+ @Test
+ public void addingStatusBarItemCallsOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem));
+ }
+
+ @Test
+ public void addingDuplicateStatusBarItemDoesNotCallOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ // Called only once for addStatusBarItem.
+ verify(mCallback, times(1))
+ .onStatusBarItemsChanged(List.of(mStatusBarItem));
+ }
+
+ @Test
+ public void removingStatusBarItemCallsOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ mProvider.removeStatusBarItem(mStatusBarItem);
+ // Called once for addStatusBarItem and once for removeStatusBarItem.
+ verify(mCallback, times(2)).onStatusBarItemsChanged(any());
+ }
+
+ @Test
+ public void removingNonexistentStatusBarItemDoesNotCallOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.removeStatusBarItem(mStatusBarItem);
+ verify(mCallback, never()).onStatusBarItemsChanged(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 60e5a94..01309f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -57,6 +57,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -94,6 +95,12 @@
DreamOverlayNotificationCountProvider mDreamOverlayNotificationCountProvider;
@Mock
StatusBarWindowStateController mStatusBarWindowStateController;
+ @Mock
+ DreamOverlayStatusBarItemsProvider mDreamOverlayStatusBarItemsProvider;
+ @Mock
+ DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem;
+ @Mock
+ View mStatusBarItemView;
private final Executor mMainExecutor = Runnable::run;
@@ -118,7 +125,8 @@
mSensorPrivacyController,
Optional.of(mDreamOverlayNotificationCountProvider),
mZenModeController,
- mStatusBarWindowStateController);
+ mStatusBarWindowStateController,
+ mDreamOverlayStatusBarItemsProvider);
}
@Test
@@ -128,6 +136,7 @@
verify(mSensorPrivacyController).addCallback(any());
verify(mZenModeController).addCallback(any());
verify(mDreamOverlayNotificationCountProvider).addCallback(any());
+ verify(mDreamOverlayStatusBarItemsProvider).addCallback(any());
}
@Test
@@ -256,7 +265,8 @@
mSensorPrivacyController,
Optional.empty(),
mZenModeController,
- mStatusBarWindowStateController);
+ mStatusBarWindowStateController,
+ mDreamOverlayStatusBarItemsProvider);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
@@ -294,6 +304,7 @@
verify(mSensorPrivacyController).removeCallback(any());
verify(mZenModeController).removeCallback(any());
verify(mDreamOverlayNotificationCountProvider).removeCallback(any());
+ verify(mDreamOverlayStatusBarItemsProvider).removeCallback(any());
}
@Test
@@ -462,4 +473,18 @@
verify(mView, never()).setVisibility(anyInt());
}
+
+ @Test
+ public void testExtraStatusBarItemSetWhenItemsChange() {
+ mController.onViewAttached();
+ when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView);
+
+ final ArgumentCaptor<DreamOverlayStatusBarItemsProvider.Callback>
+ callbackCapture = ArgumentCaptor.forClass(
+ DreamOverlayStatusBarItemsProvider.Callback.class);
+ verify(mDreamOverlayStatusBarItemsProvider).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStatusBarItemsChanged(List.of(mStatusBarItem));
+
+ verify(mView).setExtraStatusBarItemViews(List.of(mStatusBarItemView));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 642e29b..2ba8782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -22,13 +22,17 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -42,47 +46,38 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
class FooterActionsControllerTest : LeakCheckedTest() {
- @Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock
- private lateinit var userInfoController: UserInfoController
- @Mock
- private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
- @Mock
- private lateinit var multiUserSwitchController: MultiUserSwitchController
- @Mock
- private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
- @Mock
- private lateinit var globalActionsDialog: GlobalActionsDialogLite
- @Mock
- private lateinit var uiEventLogger: UiEventLogger
- @Mock
- private lateinit var securityFooterController: QSSecurityFooter
- @Mock
- private lateinit var fgsManagerController: QSFgsManagerFooter
+
+ @get:Rule var expect: Expect = Expect.create()
+
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var userInfoController: UserInfoController
+ @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
+ @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController
+ @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
+ @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var securityFooterController: QSSecurityFooter
+ @Mock private lateinit var fgsManagerController: QSFgsManagerFooter
@Captor
private lateinit var visibilityChangedCaptor:
ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
private lateinit var controller: FooterActionsController
+ private val configurationController = FakeConfigurationController()
private val metricsLogger: MetricsLogger = FakeMetricsLogger()
- private lateinit var view: FooterActionsView
private val falsingManager: FalsingManagerFake = FalsingManagerFake()
+ private lateinit var view: FooterActionsView
private lateinit var testableLooper: TestableLooper
private lateinit var fakeSettings: FakeSettings
private lateinit var securityFooter: View
@@ -90,12 +85,15 @@
@Before
fun setUp() {
+ // We want to make sure testable resources are always used
+ context.ensureTestableResources()
+
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
fakeSettings = FakeSettings()
whenever(multiUserSwitchControllerFactory.create(any()))
- .thenReturn(multiUserSwitchController)
+ .thenReturn(multiUserSwitchController)
whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
securityFooter = View(mContext)
@@ -135,7 +133,7 @@
view.findViewById<View>(R.id.pm_lite).performClick()
// Verify clicks are logged
verify(uiEventLogger, Mockito.times(1))
- .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+ .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
}
@Test
@@ -299,6 +297,86 @@
assertThat(booleanCaptor.allValues.last()).isTrue()
}
+ @Test
+ fun setExpansion_inSplitShade_alphaFollowsExpansion() {
+ enableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.25f)
+ expect.that(view.alpha).isEqualTo(0.25f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.alpha).isEqualTo(0.5f)
+
+ controller.setExpansion(0.75f)
+ expect.that(view.alpha).isEqualTo(0.75f)
+
+ controller.setExpansion(1f)
+ expect.that(view.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() {
+ enableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+ controller.setExpansion(0.9f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+ controller.setExpansion(0.91f)
+ expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f)
+
+ controller.setExpansion(0.95f)
+ expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f)
+
+ controller.setExpansion(1f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+ }
+
+ @Test
+ fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() {
+ disableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.9f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.91f)
+ expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f)
+
+ controller.setExpansion(0.95f)
+ expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f)
+
+ controller.setExpansion(1f)
+ expect.that(view.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun setExpansion_inSingleShade_backgroundAlphaAlways1() {
+ disableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+
+ controller.setExpansion(1f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+ }
+
private fun setVisibilities(
securityFooterVisible: Boolean,
fgsFooterVisible: Boolean,
@@ -311,15 +389,52 @@
}
private fun inflateView(): FooterActionsView {
- return LayoutInflater.from(context)
- .inflate(R.layout.footer_actions, null) as FooterActionsView
+ return LayoutInflater.from(context).inflate(R.layout.footer_actions, null)
+ as FooterActionsView
}
private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController {
- return FooterActionsController(view, multiUserSwitchControllerFactory,
- activityStarter, userManager, userTracker, userInfoController,
- deviceProvisionedController, securityFooterController, fgsManagerController,
- falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger,
- showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper))
+ return FooterActionsController(
+ view,
+ multiUserSwitchControllerFactory,
+ activityStarter,
+ userManager,
+ userTracker,
+ userInfoController,
+ deviceProvisionedController,
+ securityFooterController,
+ fgsManagerController,
+ falsingManager,
+ metricsLogger,
+ globalActionsDialogProvider,
+ uiEventLogger,
+ showPMLiteButton = true,
+ fakeSettings,
+ Handler(testableLooper.looper),
+ configurationController)
}
-}
\ No newline at end of file
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ configurationController.notifyConfigurationChanged()
+ }
+}
+
+private const val FLOAT_TOLERANCE = 0.01f
+
+private val View.backgroundAlphaFraction: Float?
+ get() {
+ return if (background != null) {
+ background.alpha / 255f
+ } else {
+ null
+ }
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 32c66d2..10f6ce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -205,6 +205,40 @@
}
@Test
+ public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() {
+ // Random test values without any meaning. They just have to be different from each other.
+ float expansion = 0.123f;
+ float panelExpansionFraction = 0.321f;
+ float proposedTranslation = 456f;
+ float squishinessFraction = 0.987f;
+
+ QSFragment fragment = resumeAndGetFragment();
+ enableSplitShade();
+
+ fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ squishinessFraction);
+
+ verify(mQSFooterActionController).setExpansion(panelExpansionFraction);
+ }
+
+ @Test
+ public void setQsExpansion_notInSplitShade_setsFooterActionsExpansion_basedOnExpansion() {
+ // Random test values without any meaning. They just have to be different from each other.
+ float expansion = 0.123f;
+ float panelExpansionFraction = 0.321f;
+ float proposedTranslation = 456f;
+ float squishinessFraction = 0.987f;
+
+ QSFragment fragment = resumeAndGetFragment();
+ disableSplitShade();
+
+ fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ squishinessFraction);
+
+ verify(mQSFooterActionController).setExpansion(expansion);
+ }
+
+ @Test
public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
QSFragment fragment = resumeAndGetFragment();
disableSplitShade();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 360eef9..cf5fa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -19,12 +19,14 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_STANDARD;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CHANGING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -64,12 +66,15 @@
@Test
public void testLegacyTargetExtract() {
TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE)
- .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER)
- .addChange(TRANSIT_CLOSE, 0 /* flags */)
- .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER).build();
- // Check non-wallpaper extraction
- RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrap(combined,
- false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
+ .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER,
+ createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD))
+ .addChange(TRANSIT_CLOSE, 0 /* flags */,
+ createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD))
+ .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
+ .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build();
+ // Check apps extraction
+ RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
+ mock(SurfaceControl.Transaction.class), null /* leashes */);
assertEquals(2, wrapped.length);
int changeLayer = -1;
int closeLayer = -1;
@@ -86,17 +91,25 @@
assertTrue(closeLayer < changeLayer);
// Check wallpaper extraction
- RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrap(combined,
+ RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined,
true /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
assertEquals(1, wallps.length);
assertTrue(wallps[0].prefixOrderIndex < closeLayer);
assertEquals(MODE_OPENING, wallps[0].mode);
+
+ // Check non-apps extraction
+ RemoteAnimationTargetCompat[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
+ false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
+ assertEquals(1, nonApps.length);
+ assertTrue(nonApps[0].prefixOrderIndex < closeLayer);
+ assertEquals(MODE_CHANGING, nonApps[0].mode);
}
@Test
public void testLegacyTargetWrapper() {
TransitionInfo tinfo = new TransitionInfoBuilder(TRANSIT_CLOSE)
- .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT).build();
+ .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT,
+ createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD)).build();
final TransitionInfo.Change change = tinfo.getChanges().get(0);
final Rect endBounds = new Rect(40, 60, 140, 200);
change.setTaskInfo(createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_HOME));
@@ -119,11 +132,12 @@
}
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
- @TransitionInfo.ChangeFlags int flags) {
+ @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
final TransitionInfo.Change change =
new TransitionInfo.Change(null /* token */, createMockSurface(true));
change.setMode(mode);
change.setFlags(flags);
+ change.setTaskInfo(taskInfo);
mInfo.addChange(change);
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index dfa38ab..9f21409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -68,6 +68,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.util.time.FakeSystemClock;
@@ -1715,66 +1716,201 @@
assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent());
}
- @Test(expected = IllegalStateException.class)
- public void testOutOfOrderPreGroupFilterInvalidationThrows() {
- // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
- NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(null);
+ static class CountingInvalidator {
+ CountingInvalidator(Pluggable pluggableToInvalidate) {
+ mPluggableToInvalidate = pluggableToInvalidate;
+ mInvalidationCount = 0;
+ }
+
+ public void setInvalidationCount(int invalidationCount) {
+ mInvalidationCount = invalidationCount;
+ }
+
+ public void maybeInvalidate() {
+ if (mInvalidationCount > 0) {
+ mPluggableToInvalidate.invalidateList("test invalidation");
+ mInvalidationCount--;
+ }
+ }
+
+ private Pluggable mPluggableToInvalidate;
+ private int mInvalidationCount;
+
+ private static final String TAG = "ShadeListBuilderTestCountingInvalidator";
+ }
+
+ @Test
+ public void testOutOfOrderPreGroupFilterInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // WHEN we try to run the pipeline and the filter is invalidated
- addNotif(0, PACKAGE_1);
+ // WHEN we try to run the pipeline and the filter is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderPrompterInvalidationThrows() {
- // GIVEN a NotifPromoter that gets invalidated during the sorting stage
+ public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addPreGroupFilter(filter);
+ mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+ // WHEN we try to run the pipeline and the filter is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
+ }
+
+ @Test
+ public void testNonConsecutiveOutOfOrderInvalidationDontThrowAfterTooManyRuns() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addPreGroupFilter(filter);
+ mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+ // WHEN we try to run the pipeline and the filter is invalidated at least
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception is NOT thrown.
+ }
+
+ @Test
+ public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
NotifPromoter promoter = new IdPromoter(47);
- OnBeforeSortListener listener =
- (list) -> promoter.invalidateList(null);
+ CountingInvalidator invalidator = new CountingInvalidator(promoter);
+ OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(listener);
- // WHEN we try to run the pipeline and the promoter is invalidated
+ // WHEN we try to run the pipeline and the promoter is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_1);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderComparatorInvalidationThrows() {
- // GIVEN a NotifComparator that gets invalidated during the finalizing stage
- NotifComparator comparator = new HypeComparator(PACKAGE_5);
- OnBeforeRenderListListener listener =
- (list) -> comparator.invalidateList(null);
+ public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
+ NotifPromoter promoter = new IdPromoter(47);
+ CountingInvalidator invalidator = new CountingInvalidator(promoter);
+ OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addPromoter(promoter);
+ mListBuilder.addOnBeforeSortListener(listener);
+
+ // WHEN we try to run the pipeline and the promoter is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_1);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
+ }
+
+ @Test
+ public void testOutOfOrderComparatorInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a NotifComparator that gets invalidated during the finalizing stage,
+ NotifComparator comparator = new HypeComparator(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(comparator);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.setComparators(singletonList(comparator));
mListBuilder.addOnBeforeRenderListListener(listener);
- // WHEN we try to run the pipeline and the comparator is invalidated
- addNotif(0, PACKAGE_1);
+ // WHEN we try to run the pipeline and the comparator is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderPreRenderFilterInvalidationThrows() {
- // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
- NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeRenderListListener listener = (list) -> filter.invalidateList(null);
+ public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a NotifComparator that gets invalidated during the finalizing stage,
+ NotifComparator comparator = new HypeComparator(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(comparator);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.setComparators(singletonList(comparator));
+ mListBuilder.addOnBeforeRenderListListener(listener);
+
+ // WHEN we try to run the pipeline and the comparator is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
+ }
+
+ @Test
+ public void testOutOfOrderPreRenderFilterInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.addFinalizeFilter(filter);
mListBuilder.addOnBeforeRenderListListener(listener);
- // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
- addNotif(0, PACKAGE_1);
+ // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addFinalizeFilter(filter);
+ mListBuilder.addOnBeforeRenderListListener(listener);
+
+ // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
}
@Test
@@ -2096,6 +2232,18 @@
mPipelineChoreographer.runIfScheduled();
}
+ private void runWhileScheduledUpTo(int maxRuns) {
+ int runs = 0;
+ while (mPipelineChoreographer.isScheduled()) {
+ if (runs > maxRuns) {
+ throw new IndexOutOfBoundsException(
+ "Pipeline scheduled itself more than " + maxRuns + "times");
+ }
+ runs++;
+ mPipelineChoreographer.runIfScheduled();
+ }
+ }
+
private void verifyBuiltList(ExpectedEntry ...expectedEntries) {
try {
assertEquals(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
index 44325dd..a2828d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -16,19 +16,28 @@
package com.android.systemui.statusbar.phone
+import android.app.WallpaperManager
+import android.app.WallpaperManager.OnColorsChangedListener
import android.graphics.Color
+import android.os.Handler
+import android.os.Looper
import android.testing.AndroidTestingRunner
import android.view.IWindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
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.any
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -38,17 +47,41 @@
private val fakeSystemClock = FakeSystemClock()
private val fakeExecutor = FakeExecutor(fakeSystemClock)
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ @get:Rule var expect: Expect = Expect.create()
@Mock private lateinit var windowManager: IWindowManager
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var wallpaperManager: WallpaperManager
private lateinit var provider: LetterboxBackgroundProvider
+ private var wallpaperColorsListener: OnColorsChangedListener? = null
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager)
+ setUpWallpaperManager()
+ provider =
+ LetterboxBackgroundProvider(
+ windowManager, fakeExecutor, dumpManager, wallpaperManager, mainHandler)
+ }
+
+ private fun setUpWallpaperManager() {
+ doAnswer { invocation ->
+ wallpaperColorsListener = invocation.arguments[0] as OnColorsChangedListener
+ return@doAnswer Unit
+ }
+ .`when`(wallpaperManager)
+ .addOnColorsChangedListener(any(), eq(mainHandler))
+ doAnswer {
+ wallpaperColorsListener = null
+ return@doAnswer Unit
+ }
+ .`when`(wallpaperManager)
+ .removeOnColorsChangedListener(any(OnColorsChangedListener::class.java))
}
@Test
@@ -76,6 +109,31 @@
}
@Test
+ fun letterboxBackgroundColor_returnsValueFromWindowManagerOnlyOnce() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+ provider.start()
+ fakeExecutor.runAllReady()
+ expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN)
+ fakeExecutor.runAllReady()
+ expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterWallpaperChanges_returnsUpdatedColor() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN)
+ wallpaperColorsListener!!.onColorsChanged(null, 0)
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.GREEN)
+ }
+
+ @Test
fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() {
assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false)
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 74ee680..4f3fd64 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -66,6 +66,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -708,8 +709,8 @@
private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
String suffixFormat, long idNumber) {
- final String suffix = String.format(suffixFormat, idNumber);
- final String filename = String.format(CONFIG_FILE_FORMAT, suffix);
+ final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber);
+ final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix);
final File filePath = Environment.buildPath(
baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename);
final DisplayDeviceConfig config = new DisplayDeviceConfig(context);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 75ee447..9c86076 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1315,9 +1315,6 @@
}
final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
- if (autoBrightnessAdjustmentChanged) {
- mTemporaryAutoBrightnessAdjustment = Float.NaN;
- }
// Use the autobrightness adjustment override if set.
final float autoBrightnessAdjustment;
@@ -2293,14 +2290,15 @@
private void handleSettingsChange(boolean userSwitch) {
mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+ mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
if (userSwitch) {
// Don't treat user switches as user initiated change.
setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+ updateAutoBrightnessAdjustment();
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
}
}
- mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
// We don't bother with a pending variable for VR screen brightness since we just
// immediately adapt to it.
mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
@@ -2369,6 +2367,7 @@
}
mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
mPendingAutoBrightnessAdjustment = Float.NaN;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
return true;
}
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index af2b902..8582f54 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -23,11 +23,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
@@ -35,12 +32,11 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
+import android.view.View;
+import android.view.Window;
import android.view.WindowManager;
import com.android.internal.R;
@@ -48,49 +44,48 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
/**
* Defines behavior for handling interactions between power button events and fingerprint-related
* operations, for devices where the fingerprint sensor (side fps) lives on the power button.
*/
-public class SideFpsEventHandler {
+public class SideFpsEventHandler implements View.OnClickListener {
private static final int DEBOUNCE_DELAY_MILLIS = 500;
- private int getTapWaitForPowerDuration(Context context) {
- int tap = context.getResources().getInteger(
- R.integer.config_sidefpsEnrollPowerPressWindow);
- if (Build.isDebuggable()) {
- tap = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap,
- UserHandle.USER_CURRENT);
- }
- return tap;
- }
-
private static final String TAG = "SideFpsEventHandler";
- @NonNull private final Context mContext;
- @NonNull private final Handler mHandler;
- @NonNull private final PowerManager mPowerManager;
- @NonNull private final Supplier<AlertDialog.Builder> mDialogSupplier;
- @NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
-
- @Nullable private Dialog mDialog;
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final PowerManager mPowerManager;
+ @NonNull
+ private final AtomicBoolean mSideFpsEventHandlerReady;
+ private final int mDismissDialogTimeout;
+ @Nullable
+ private SideFpsToast mDialog;
private final Runnable mTurnOffDialog =
() -> {
dismissDialog("mTurnOffDialog");
};
-
- @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener;
-
private @BiometricStateListener.State int mBiometricState;
- private final int mTapWaitForPowerDuration;
private FingerprintManager mFingerprintManager;
+ private DialogProvider mDialogProvider;
+ private long mLastPowerPressTime;
- SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
- this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
+ SideFpsEventHandler(
+ Context context,
+ Handler handler,
+ PowerManager powerManager) {
+ this(context, handler, powerManager, (ctx) -> {
+ SideFpsToast dialog = new SideFpsToast(ctx);
+ dialog.getWindow()
+ .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ });
}
@VisibleForTesting
@@ -98,23 +93,13 @@
Context context,
Handler handler,
PowerManager powerManager,
- Supplier<AlertDialog.Builder> dialogSupplier) {
+ DialogProvider provider) {
mContext = context;
mHandler = handler;
mPowerManager = powerManager;
- mDialogSupplier = dialogSupplier;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
- mDialogDismissListener =
- (dialog) -> {
- if (mDialog == dialog) {
- if (mHandler != null) {
- mHandler.removeCallbacks(mTurnOffDialog);
- }
- mDialog = null;
- }
- };
-
+ mDialogProvider = provider;
// ensure dialog is dismissed if screen goes off for unrelated reasons
context.registerReceiver(
new BroadcastReceiver() {
@@ -127,7 +112,13 @@
}
},
new IntentFilter(Intent.ACTION_SCREEN_OFF));
- mTapWaitForPowerDuration = getTapWaitForPowerDuration(context);
+ mDismissDialogTimeout = context.getResources().getInteger(
+ R.integer.config_sideFpsToastTimeout);
+ }
+
+ @Override
+ public void onClick(View v) {
+ goToSleep(mLastPowerPressTime);
}
/**
@@ -165,8 +156,9 @@
Log.v(TAG, "Detected a tap to turn off dialog, ignoring");
mHandler.removeCallbacks(mTurnOffDialog);
}
+ showDialog(eventTime, "Enroll Power Press");
+ mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
});
- showDialog(eventTime, "Enroll Power Press");
return true;
case STATE_BP_AUTH:
return true;
@@ -176,54 +168,11 @@
}
}
- @NonNull
- private static Dialog showConfirmDialog(
- @NonNull AlertDialog.Builder dialogBuilder,
- @NonNull PowerManager powerManager,
- long eventTime,
- @BiometricStateListener.State int biometricState,
- @NonNull DialogInterface.OnDismissListener dismissListener) {
- final boolean enrolling = biometricState == STATE_ENROLLING;
- final int title =
- enrolling
- ? R.string.fp_power_button_enrollment_title
- : R.string.fp_power_button_bp_title;
- final int message =
- enrolling
- ? R.string.fp_power_button_enrollment_message
- : R.string.fp_power_button_bp_message;
- final int positiveText =
- enrolling
- ? R.string.fp_power_button_enrollment_positive_button
- : R.string.fp_power_button_bp_positive_button;
- final int negativeText =
- enrolling
- ? R.string.fp_power_button_enrollment_negative_button
- : R.string.fp_power_button_bp_negative_button;
-
- final Dialog confirmScreenOffDialog =
- dialogBuilder
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(
- positiveText,
- (dialog, which) -> {
- dialog.dismiss();
- powerManager.goToSleep(
- eventTime,
- PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
- 0 /* flags */);
- })
- .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
- .setOnDismissListener(dismissListener)
- .setCancelable(false)
- .create();
- confirmScreenOffDialog
- .getWindow()
- .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- confirmScreenOffDialog.show();
-
- return confirmScreenOffDialog;
+ private void goToSleep(long eventTime) {
+ mPowerManager.goToSleep(
+ eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+ 0 /* flags */);
}
/**
@@ -247,7 +196,8 @@
if (fingerprintManager.isPowerbuttonFps()) {
fingerprintManager.registerBiometricStateListener(
new BiometricStateListener() {
- @Nullable private Runnable mStateRunnable = null;
+ @Nullable
+ private Runnable mStateRunnable = null;
@Override
public void onStateChanged(
@@ -281,13 +231,6 @@
public void onBiometricAction(
@BiometricStateListener.Action int action) {
Log.d(TAG, "onBiometricAction " + action);
- switch (action) {
- case BiometricStateListener.ACTION_SENSOR_TOUCH:
- mHandler.postDelayed(
- mTurnOffDialog,
- mTapWaitForPowerDuration);
- break;
- }
}
});
mSideFpsEventHandlerReady.set(true);
@@ -309,12 +252,13 @@
Log.d(TAG, "Ignoring show dialog");
return;
}
- mDialog =
- showConfirmDialog(
- mDialogSupplier.get(),
- mPowerManager,
- time,
- mBiometricState,
- mDialogDismissListener);
+ mDialog = mDialogProvider.provideDialog(mContext);
+ mLastPowerPressTime = time;
+ mDialog.show();
+ mDialog.setOnClickListener(this);
}
-}
+
+ interface DialogProvider {
+ SideFpsToast provideDialog(Context context);
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java
new file mode 100644
index 0000000..db07467
--- /dev/null
+++ b/services/core/java/com/android/server/policy/SideFpsToast.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.policy;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import com.android.internal.R;
+
+/**
+ * Toast for side fps. This is typically shown during enrollment
+ * when a user presses the power button.
+ *
+ * This dialog is used by {@link SideFpsEventHandler}
+ */
+public class SideFpsToast extends Dialog {
+
+ SideFpsToast(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.side_fps_toast);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ final Window window = this.getWindow();
+ WindowManager.LayoutParams windowParams = window.getAttributes();
+ windowParams.dimAmount = 0;
+ windowParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+ windowParams.gravity = Gravity.BOTTOM;
+ window.setAttributes(windowParams);
+ }
+
+ /**
+ * Sets the onClickListener for the toast dialog.
+ * @param listener
+ */
+ public void setOnClickListener(View.OnClickListener listener) {
+ final Button turnOffScreen = findViewById(R.id.turn_off_screen);
+ if (turnOffScreen != null) {
+ turnOffScreen.setOnClickListener(listener);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2ba0e23..75552e0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -489,6 +489,7 @@
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
private int mForceHiddenFlags = 0;
+ private boolean mForceTranslucent = false;
// TODO(b/160201781): Revisit double invocation issue in Task#removeChild.
/**
@@ -4348,6 +4349,10 @@
return true;
}
+ void setForceTranslucent(boolean set) {
+ mForceTranslucent = set;
+ }
+
@Override
public boolean isAlwaysOnTop() {
return !isForceHidden() && super.isAlwaysOnTop();
@@ -4366,6 +4371,11 @@
}
@Override
+ protected boolean isForceTranslucent() {
+ return mForceTranslucent;
+ }
+
+ @Override
long getProtoFieldId() {
return TASK;
}
@@ -5339,7 +5349,7 @@
abort = true;
}
if (abort) {
- Slog.e(TAG, "Cannot navigateUpTo, intent =" + destIntent);
+ android.util.EventLog.writeEvent(0x534e4554, "238605611", callingUid, "");
foundParentInTask = false;
} else {
parent.deliverNewIntentLocked(callingUid, destIntent, destGrants,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 8d94324..5f85a14 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -740,6 +740,10 @@
return false;
}
+ protected boolean isForceTranslucent() {
+ return false;
+ }
+
boolean isLeafTaskFragment() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).asTaskFragment() != null) {
@@ -865,7 +869,7 @@
*/
@VisibleForTesting
boolean isTranslucent(ActivityRecord starting) {
- if (!isAttached() || isForceHidden()) {
+ if (!isAttached() || isForceHidden() || isForceTranslucent()) {
return true;
}
final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 29e407f..5a2100d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -645,6 +645,12 @@
}
}
+ if ((c.getChangeMask()
+ & WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) {
+ tr.setForceTranslucent(c.getForceTranslucent());
+ effects = TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
final int childWindowingMode = c.getActivityWindowingMode();
if (childWindowingMode > -1) {
tr.setActivityWindowingMode(childWindowingMode);
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 7746bd6..0635cc4 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -24,7 +24,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AlertDialog;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.fingerprint.FingerprintManager;
@@ -36,10 +35,13 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
+import android.testing.TestableResources;
import android.view.Window;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -47,7 +49,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.util.List;
@@ -68,17 +69,22 @@
BiometricStateListener.STATE_BP_AUTH,
BiometricStateListener.STATE_AUTH_OTHER);
+ private static final Integer AUTO_DISMISS_DIALOG = 500;
+
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
- @Mock private PackageManager mPackageManager;
- @Mock private FingerprintManager mFingerprintManager;
- @Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
- @Mock private AlertDialog mAlertDialog;
- @Mock private Window mWindow;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private SideFpsToast mDialog;
+ @Mock
+ private Window mWindow;
- private TestLooper mLooper = new TestLooper();
+ private final TestLooper mLooper = new TestLooper();
private SideFpsEventHandler mEventHandler;
private BiometricStateListener mBiometricStateListener;
@@ -88,16 +94,17 @@
mContext.addMockSystemService(PackageManager.class, mPackageManager);
mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_sideFpsToastTimeout, AUTO_DISMISS_DIALOG);
- when(mDialogBuilder.create()).thenReturn(mAlertDialog);
- when(mAlertDialog.getWindow()).thenReturn(mWindow);
+ when(mDialog.getWindow()).thenReturn(mWindow);
mEventHandler =
new SideFpsEventHandler(
mContext,
new Handler(mLooper.getLooper()),
mContext.getSystemService(PowerManager.class),
- () -> mDialogBuilder);
+ (ctx) -> mDialog);
}
@Test
@@ -108,7 +115,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
@@ -120,7 +127,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
}
@@ -133,7 +140,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
}
@@ -148,7 +155,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
@@ -159,18 +166,18 @@
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
- public void promptsWhenBPisActive() throws Exception {
+ public void doesNotpromptWhenBPisActive() throws Exception {
setupWithSensor(true /* hasSideFps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_BP_AUTH);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
@@ -181,57 +188,40 @@
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
- verify(mAlertDialog, never()).dismiss();
+ verify(mDialog).show();
}
@Test
- public void dismissesDialogOnTouchWhenEnrolling() throws Exception {
+ public void dialogDismissesAfterTime() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
- when(mAlertDialog.isShowing()).thenReturn(true);
+ when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
-
- mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- mLooper.moveTimeForward(10000);
+ verify(mDialog).show();
+ mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
mLooper.dispatchAll();
-
- verify(mAlertDialog).dismiss();
+ verify(mDialog).dismiss();
}
@Test
- public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception {
+ public void dialogDoesNotDismissOnSensorTouch() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
- when(mAlertDialog.isShowing()).thenReturn(true);
+ when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
+ verify(mDialog).show();
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
-
+ mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
mLooper.dispatchAll();
- verify(mAlertDialog, never()).dismiss();
- }
- @Test
- public void showDialogAfterTap() throws Exception {
- setupWithSensor(true /* hasSfps */, true /* initialized */);
-
- setBiometricState(BiometricStateListener.STATE_ENROLLING);
- when(mAlertDialog.isShowing()).thenReturn(true);
- mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
-
- mLooper.dispatchAll();
- verify(mAlertDialog).show();
+ verify(mDialog, never()).dismiss();
}
private void setBiometricState(@BiometricStateListener.State int newState) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 84c2c55..7d4e6fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -578,6 +578,22 @@
}
@Test
+ public void testContainerTranslucentChanges() {
+ removeGlobalMinSizeRestriction();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ assertFalse(rootTask.isTranslucent(activity));
+ t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), true);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+ assertTrue(rootTask.isTranslucent(activity));
+ t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), false);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+ assertFalse(rootTask.isTranslucent(activity));
+ }
+
+ @Test
public void testSetIgnoreOrientationRequest_taskDisplayArea() {
removeGlobalMinSizeRestriction();
final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();