Merge "Initialize horizontal panel as GONE and add logs" into tm-qpr-dev
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 77589a2..a7d4342b 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -15,14 +15,6 @@
"**/*.java",
"**/*.aidl",
],
- exclude_srcs: [
- // Remove election toolbar code from build time
- "android/service/selectiontoolbar/*.aidl",
- "android/service/selectiontoolbar/*.java",
- "android/view/selectiontoolbar/*.aidl",
- "android/view/selectiontoolbar/*.java",
- "com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java",
- ],
visibility: ["//frameworks/base"],
}
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/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 4019283..6615374 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -230,6 +230,8 @@
import android.view.contentcapture.IContentCaptureManager;
import android.view.displayhash.DisplayHashManager;
import android.view.inputmethod.InputMethodManager;
+import android.view.selectiontoolbar.ISelectionToolbarManager;
+import android.view.selectiontoolbar.SelectionToolbarManager;
import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
import android.view.translation.ITranslationManager;
@@ -363,6 +365,15 @@
return new TextClassificationManager(ctx);
}});
+ registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class,
+ new CachedServiceFetcher<SelectionToolbarManager>() {
+ @Override
+ public SelectionToolbarManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE);
+ return new SelectionToolbarManager(ctx.getOuterContext(),
+ ISelectionToolbarManager.Stub.asInterface(b));
+ }});
+
registerService(Context.FONT_SERVICE, FontManager.class,
new CachedServiceFetcher<FontManager>() {
@Override
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/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index d75ff2f..5721fa6 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -422,7 +422,7 @@
public void relayout(WindowManager.LayoutParams attrs,
WindowlessWindowManager.ResizeCompleteCallback callback) {
mViewRoot.setLayoutParams(attrs, false);
- mViewRoot.setReportNextDraw(true /* syncBuffer */);
+ mViewRoot.setReportNextDraw(true /* syncBuffer */, "scvh_relayout");
mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fa37ca1..fff6c60 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -586,8 +586,21 @@
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
boolean mPerformContentCapture;
-
boolean mReportNextDraw;
+ /** Set only while mReportNextDraw=true, indicating the last reason that was triggered */
+ String mLastReportNextDrawReason;
+ /** The reaason the last call to performDraw() returned false */
+ String mLastPerformDrawSkippedReason;
+ /** The reason the last call to performTraversals() returned without drawing */
+ String mLastPerformTraversalsSkipDrawReason;
+ /** The state of the local sync, if one is in progress. Can be one of the states below. */
+ int mLocalSyncState;
+
+ // The possible states of the local sync, see createSyncIfNeeded()
+ private final int LOCAL_SYNC_NONE = 0;
+ private final int LOCAL_SYNC_PENDING = 1;
+ private final int LOCAL_SYNC_RETURNED = 2;
+ private final int LOCAL_SYNC_MERGED = 3;
/**
* Set whether the draw should send the buffer to system server. When set to true, VRI will
@@ -1811,7 +1824,7 @@
mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
if (msg == MSG_RESIZED_REPORT) {
- reportNextDraw();
+ reportNextDraw("resized");
}
if (mView != null && (frameChanged || configChanged)) {
@@ -2716,6 +2729,8 @@
}
private void performTraversals() {
+ mLastPerformTraversalsSkipDrawReason = null;
+
// cache mView since it is used so much below...
final View host = mView;
if (DBG) {
@@ -2725,12 +2740,14 @@
}
if (host == null || !mAdded) {
+ mLastPerformTraversalsSkipDrawReason = host == null ? "no_host" : "not_added";
return;
}
mIsInTraversal = true;
mWillDrawSoon = true;
boolean cancelDraw = false;
+ String cancelReason = null;
boolean isSyncRequest = false;
boolean windowSizeMayChange = false;
@@ -3013,13 +3030,14 @@
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
== RELAYOUT_RES_CANCEL_AND_REDRAW;
+ cancelReason = "relayout";
final boolean dragResizing = mPendingDragResizing;
if (mSyncSeqId > mLastSyncSeqId) {
mLastSyncSeqId = mSyncSeqId;
if (DEBUG_BLAST) {
Log.d(mTag, "Relayout called with blastSync");
}
- reportNextDraw();
+ reportNextDraw("relayout");
mSyncBuffer = true;
isSyncRequest = true;
if (!cancelDraw) {
@@ -3117,6 +3135,7 @@
}
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
+ mLastPerformTraversalsSkipDrawReason = "oom_initialize_renderer";
return;
}
}
@@ -3154,6 +3173,7 @@
mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
+ mLastPerformTraversalsSkipDrawReason = "oom_update_surface";
return;
}
}
@@ -3319,6 +3339,7 @@
if (mCheckIfCanDraw) {
try {
cancelDraw = mWindowSession.cancelDraw(mWindow);
+ cancelReason = "wm_sync";
if (DEBUG_BLAST) {
Log.d(mTag, "cancelDraw returned " + cancelDraw);
}
@@ -3541,19 +3562,21 @@
mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
- reportNextDraw();
+ reportNextDraw("first_relayout");
}
mCheckIfCanDraw = isSyncRequest || cancelDraw;
- boolean cancelAndRedraw =
- mAttachInfo.mTreeObserver.dispatchOnPreDraw() || (cancelDraw && mDrewOnceForSync);
+ boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+ boolean cancelAndRedraw = cancelDueToPreDrawListener
+ || (cancelDraw && mDrewOnceForSync);
if (!cancelAndRedraw) {
createSyncIfNeeded();
mDrewOnceForSync = true;
}
if (!isViewVisible) {
+ mLastPerformTraversalsSkipDrawReason = "view_not_visible";
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
@@ -3565,6 +3588,9 @@
mSyncBufferCallback.onBufferReady(null);
}
} else if (cancelAndRedraw) {
+ mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
+ ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
+ : "cancel_" + cancelReason;
// Try again
scheduleTraversals();
} else {
@@ -3588,11 +3614,13 @@
if (!cancelAndRedraw) {
mReportNextDraw = false;
+ mLastReportNextDrawReason = null;
mSyncBufferCallback = null;
mSyncBuffer = false;
if (isInLocalSync()) {
mSurfaceSyncer.markSyncReady(mSyncId);
mSyncId = UNSET_SYNC_ID;
+ mLocalSyncState = LOCAL_SYNC_NONE;
}
}
}
@@ -3604,9 +3632,12 @@
}
final int seqId = mSyncSeqId;
+ mLocalSyncState = LOCAL_SYNC_PENDING;
mSyncId = mSurfaceSyncer.setupSync(transaction -> {
+ mLocalSyncState = LOCAL_SYNC_RETURNED;
// Callback will be invoked on executor thread so post to main thread.
mHandler.postAtFrontOfQueue(() -> {
+ mLocalSyncState = LOCAL_SYNC_MERGED;
mSurfaceChangedTransaction.merge(transaction);
reportDrawFinished(seqId);
});
@@ -4321,9 +4352,12 @@
}
private boolean performDraw() {
+ mLastPerformDrawSkippedReason = null;
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
+ mLastPerformDrawSkippedReason = "screen_off";
return false;
} else if (mView == null) {
+ mLastPerformDrawSkippedReason = "no_root_view";
return false;
}
@@ -8390,6 +8424,21 @@
if (mTraversalScheduled) {
writer.println(innerPrefix + " (barrier=" + mTraversalBarrier + ")");
}
+ writer.println(innerPrefix + "mReportNextDraw=" + mReportNextDraw);
+ if (mReportNextDraw) {
+ writer.println(innerPrefix + " (reason=" + mLastReportNextDrawReason + ")");
+ }
+ if (mLastPerformTraversalsSkipDrawReason != null) {
+ writer.println(innerPrefix + "mLastPerformTraversalsFailedReason="
+ + mLastPerformTraversalsSkipDrawReason);
+ }
+ if (mLastPerformDrawSkippedReason != null) {
+ writer.println(innerPrefix + "mLastPerformDrawFailedReason="
+ + mLastPerformDrawSkippedReason);
+ }
+ if (mLocalSyncState != LOCAL_SYNC_NONE) {
+ writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
+ }
writer.println(innerPrefix + "mIsAmbientMode=" + mIsAmbientMode);
writer.println(innerPrefix + "mUnbufferedInputSource="
+ Integer.toHexString(mUnbufferedInputSource));
@@ -9890,11 +9939,12 @@
}
}
- private void reportNextDraw() {
+ private void reportNextDraw(String reason) {
if (DEBUG_BLAST) {
Log.d(mTag, "reportNextDraw " + Debug.getCallers(5));
}
mReportNextDraw = true;
+ mLastReportNextDrawReason = reason;
}
/**
@@ -9907,11 +9957,12 @@
* @param syncBuffer If true, the transaction that contains the buffer from the draw should be
* sent to system to be synced. If false, VRI will not try to sync the buffer,
* but only report back that a buffer was drawn.
+ * @param reason A debug string indicating the reason for reporting the next draw
* @hide
*/
- public void setReportNextDraw(boolean syncBuffer) {
+ public void setReportNextDraw(boolean syncBuffer, String reason) {
mSyncBuffer = syncBuffer;
- reportNextDraw();
+ reportNextDraw(reason);
invalidate();
}
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/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index ed8350a..fd62ecd 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -74,6 +74,9 @@
* that the listener will be immediately called. */
private boolean mWindowShown;
+ // The reason that the last call to dispatchOnPreDraw() returned true to cancel and redraw
+ private String mLastDispatchOnPreDrawCanceledReason;
+
private boolean mAlive = true;
/**
@@ -1167,6 +1170,7 @@
*/
@SuppressWarnings("unchecked")
public final boolean dispatchOnPreDraw() {
+ mLastDispatchOnPreDrawCanceledReason = null;
boolean cancelDraw = false;
final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
if (listeners != null && listeners.size() > 0) {
@@ -1174,7 +1178,11 @@
try {
int count = access.size();
for (int i = 0; i < count; i++) {
- cancelDraw |= !(access.get(i).onPreDraw());
+ final OnPreDrawListener preDrawListener = access.get(i);
+ cancelDraw |= !(preDrawListener.onPreDraw());
+ if (cancelDraw) {
+ mLastDispatchOnPreDrawCanceledReason = preDrawListener.getClass().getName();
+ }
}
} finally {
listeners.end();
@@ -1184,6 +1192,15 @@
}
/**
+ * @return the reason that the last call to dispatchOnPreDraw() returned true to cancel the
+ * current draw, or null if the last call did not cancel.
+ * @hide
+ */
+ final String getLastDispatchOnPreDrawCanceledReason() {
+ return mLastDispatchOnPreDrawCanceledReason;
+ }
+
+ /**
* Notifies registered listeners that the window is now shown
* @hide
*/
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/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 5eec054..a339062 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1587,7 +1587,13 @@
public BitmapCache(Parcel source) {
mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
- mBitmapHashes = source.readSparseIntArray();
+ mBitmapHashes = new SparseIntArray();
+ for (int i = 0; i < mBitmaps.size(); i++) {
+ Bitmap b = mBitmaps.get(i);
+ if (b != null) {
+ mBitmapHashes.put(b.hashCode(), i);
+ }
+ }
}
public int getBitmapId(Bitmap b) {
@@ -1603,7 +1609,7 @@
b = b.asShared();
}
mBitmaps.add(b);
- mBitmapHashes.put(mBitmaps.size() - 1, hash);
+ mBitmapHashes.put(hash, mBitmaps.size() - 1);
mBitmapMemory = -1;
return (mBitmaps.size() - 1);
}
@@ -1620,7 +1626,6 @@
public void writeBitmapsToParcel(Parcel dest, int flags) {
dest.writeTypedList(mBitmaps, flags);
- dest.writeSparseIntArray(mBitmapHashes);
}
public int getBitmapMemory() {
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/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 1d396be..0730f3d 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -325,7 +325,8 @@
public boolean checkApplicationCallbackRegistration(int priority,
OnBackInvokedCallback callback) {
if (!mApplicationCallBackEnabled
- && !(callback instanceof CompatOnBackInvokedCallback)) {
+ && !(callback instanceof CompatOnBackInvokedCallback)
+ && !ALWAYS_ENFORCE_PREDICTIVE_BACK) {
Log.w("OnBackInvokedCallback",
"OnBackInvokedCallback is not enabled for the application."
+ "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the"
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
index c484525..f7af67b 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.view.MenuItem;
import android.view.View;
+import android.view.selectiontoolbar.SelectionToolbarManager;
import android.widget.PopupWindow;
import java.util.List;
@@ -92,7 +93,10 @@
* enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation.
*/
static FloatingToolbarPopup createInstance(Context context, View parent) {
- return new LocalFloatingToolbarPopup(context, parent);
+ boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context);
+ return enabled
+ ? new RemoteFloatingToolbarPopup(context, parent)
+ : new LocalFloatingToolbarPopup(context, parent);
}
}
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/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 5cba3b4..6ae0f9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -55,6 +55,7 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -72,6 +73,7 @@
*/
public class ShellTaskOrganizer extends TaskOrganizer implements
CompatUIController.CompatUICallback {
+ private static final String TAG = "ShellTaskOrganizer";
// Intentionally using negative numbers here so the positive numbers can be used
// for task id specific listeners that will be added later.
@@ -90,8 +92,6 @@
})
public @interface TaskListenerType {}
- private static final String TAG = "ShellTaskOrganizer";
-
/**
* Callbacks for when the tasks change in the system.
*/
@@ -177,6 +177,9 @@
@Nullable
private final CompatUIController mCompatUI;
+ @NonNull
+ private final ShellCommandHandler mShellCommandHandler;
+
@Nullable
private final Optional<RecentTasksController> mRecentTasks;
@@ -187,29 +190,33 @@
private RunningTaskInfo mLastFocusedTaskInfo;
public ShellTaskOrganizer(ShellExecutor mainExecutor) {
- this(null /* shellInit */, null /* taskOrganizerController */, null /* compatUI */,
+ this(null /* shellInit */, null /* shellCommandHandler */,
+ null /* taskOrganizerController */, null /* compatUI */,
Optional.empty() /* unfoldAnimationController */,
Optional.empty() /* recentTasksController */,
mainExecutor);
}
public ShellTaskOrganizer(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
@Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
- this(shellInit, null /* taskOrganizerController */, compatUI,
+ this(shellInit, shellCommandHandler, null /* taskOrganizerController */, compatUI,
unfoldAnimationController, recentTasks, mainExecutor);
}
@VisibleForTesting
protected ShellTaskOrganizer(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
@Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
super(taskOrganizerController, mainExecutor);
+ mShellCommandHandler = shellCommandHandler;
mCompatUI = compatUI;
mRecentTasks = recentTasks;
mUnfoldAnimationController = unfoldAnimationController.orElse(null);
@@ -219,6 +226,7 @@
}
private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
if (mCompatUI != null) {
mCompatUI.setCompatUICallback(this);
}
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/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 05fafc5..d3e46f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -57,6 +57,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -90,13 +91,14 @@
* Raw delta between {@link #mInitTouchLocation} and the last touch location.
*/
private final Point mTouchEventDelta = new Point();
- private final ShellExecutor mShellExecutor;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
/** Tracks if an uninterruptible transition is in progress */
private boolean mTransitionInProgress = false;
+ /** Tracks if we should start the back gesture on the next motion move event */
+ private boolean mShouldStartOnNextMoveEvent = false;
/** @see #setTriggerBack(boolean) */
private boolean mTriggerBack;
@@ -105,6 +107,9 @@
private final SurfaceControl.Transaction mTransaction;
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final ShellExecutor mShellExecutor;
+ private final Handler mBgHandler;
@Nullable
private IOnBackInvokedCallback mBackToLauncherCallback;
private float mTriggerThreshold;
@@ -133,16 +138,19 @@
};
public BackAnimationController(
+ @NonNull ShellInit shellInit,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
- this(shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
+ this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
ActivityTaskManager.getService(), context, context.getContentResolver());
}
@VisibleForTesting
- BackAnimationController(@NonNull @ShellMainThread ShellExecutor shellExecutor,
- @NonNull @ShellBackgroundThread Handler handler,
+ BackAnimationController(
+ @NonNull ShellInit shellInit,
+ @NonNull @ShellMainThread ShellExecutor shellExecutor,
+ @NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
@@ -150,7 +158,13 @@
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
mContext = context;
- setupAnimationDeveloperSettingsObserver(contentResolver, handler);
+ mContentResolver = contentResolver;
+ mBgHandler = bgHandler;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -286,12 +300,17 @@
if (mTransitionInProgress) {
return;
}
- if (keyAction == MotionEvent.ACTION_MOVE) {
+ if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
+ mShouldStartOnNextMoveEvent = true;
+ }
+ } else if (keyAction == MotionEvent.ACTION_MOVE) {
+ if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) {
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation.
onGestureStarted(touchX, touchY);
+ mShouldStartOnNextMoveEvent = false;
}
onMove(touchX, touchY, swipeEdge);
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
@@ -425,6 +444,11 @@
private void onGestureFinished(boolean fromTouch) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+ if (!mBackGestureStarted) {
+ finishAnimation();
+ return;
+ }
+
if (fromTouch) {
// Let touch reset the flag otherwise it will start a new back navigation and refresh
// the info when received a new move event.
@@ -540,6 +564,7 @@
boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null;
mTriggerBack = false;
+ mShouldStartOnNextMoveEvent = false;
if (backNavigationInfo == null) {
return;
}
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/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index db8d9d4..235fd9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -43,6 +43,7 @@
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
@@ -119,6 +120,7 @@
private boolean mKeyguardShowing;
public CompatUIController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -134,10 +136,14 @@
mSyncQueue = syncQueue;
mMainExecutor = mainExecutor;
mTransitionsLazy = transitionsLazy;
+ mCompatUIHintsState = new CompatUIHintsState();
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addKeyguardChangeListener(this);
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
- mCompatUIHintsState = new CompatUIHintsState();
- shellController.addKeyguardChangeListener(this);
}
/** Sets the callback for UI interactions. */
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 3add417..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);
}
@@ -174,13 +165,14 @@
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new ShellTaskOrganizer(shellInit, compatUI, unfoldAnimationController,
- recentTasksOptional, mainExecutor);
+ return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI,
+ unfoldAnimationController, recentTasksOptional, mainExecutor);
}
@WMSingleton
@@ -188,6 +180,7 @@
static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -196,19 +189,20 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler
) {
- return new KidsModeTaskOrganizer(context, shellInit, syncTransactionQueue,
- displayController, displayInsetsController, unfoldAnimationController,
- recentTasksOptional, mainExecutor, mainHandler);
+ return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
+ syncTransactionQueue, displayController, displayInsetsController,
+ unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
}
@WMSingleton
@Provides
static CompatUIController provideCompatUIController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
- return new CompatUIController(context, shellController, displayController,
+ return new CompatUIController(context, shellInit, shellController, displayController,
displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
}
@@ -268,12 +262,14 @@
@Provides
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
+ ShellInit shellInit,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellExecutor, backgroundHandler, context));
+ new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
+ context));
}
return Optional.empty();
}
@@ -384,11 +380,14 @@
@WMSingleton
@Provides
static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
- ShellController shellController, DisplayController displayController,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(
- HideDisplayCutoutController.create(context, shellController, displayController,
- mainExecutor));
+ HideDisplayCutoutController.create(context, shellInit, shellCommandHandler,
+ shellController, displayController, mainExecutor));
}
//
@@ -466,12 +465,13 @@
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, taskStackListener,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellCommandHandler,
+ taskStackListener, mainExecutor));
}
//
@@ -649,8 +649,9 @@
@WMSingleton
@Provides
static ShellController provideShellController(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(shellInit, mainExecutor);
+ return new ShellController(shellInit, shellCommandHandler, mainExecutor);
}
//
@@ -676,12 +677,15 @@
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
+ Optional<Pip> pipOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
+ Optional<OneHandedController> oneHandedControllerOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
ActivityEmbeddingController activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
@@ -697,18 +701,7 @@
@WMSingleton
@Provides
- static ShellCommandHandler provideShellCommandHandlerImpl(
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<RecentTasksController> recentTasksOptional,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new ShellCommandHandler(shellController, shellTaskOrganizer,
- kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, recentTasksOptional, mainExecutor);
+ static ShellCommandHandler provideShellCommandHandler() {
+ return new ShellCommandHandler();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index d53451a..2ca9c3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -74,6 +74,7 @@
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.SplitscreenPipMixedHandler;
@@ -248,14 +249,20 @@
@Provides
@DynamicOverride
static OneHandedController provideOneHandedController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
- WindowManager windowManager, DisplayController displayController,
- DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor,
- @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
- return OneHandedController.create(context, shellController, windowManager,
- displayController, displayLayout, taskStackListener, jankMonitor, uiEventLogger,
- mainExecutor, mainHandler);
+ WindowManager windowManager,
+ DisplayController displayController,
+ DisplayLayout displayLayout,
+ TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger,
+ InteractionJankMonitor jankMonitor,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
+ windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
+ uiEventLogger, mainExecutor, mainHandler);
}
//
@@ -268,6 +275,7 @@
static SplitScreenController provideSplitScreenController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
@@ -281,10 +289,10 @@
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
@ShellMainThread ShellExecutor mainExecutor) {
- return new SplitScreenController(context, shellInit, shellController, shellTaskOrganizer,
- syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController,
- displayInsetsController, dragAndDropController, transitions, transactionPool,
- iconProvider, recentTasks, mainExecutor);
+ return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
+ shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
+ displayImeController, displayInsetsController, dragAndDropController, transitions,
+ transactionPool, iconProvider, recentTasks, mainExecutor);
}
//
@@ -294,24 +302,33 @@
@WMSingleton
@Provides
static Optional<Pip> providePip(Context context,
- ShellController shellController, DisplayController displayController,
- PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
- PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
+ PipAppOpsListener pipAppOpsListener,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper,
+ PipMediaController pipMediaController,
+ PhonePipMenuController phonePipMenuController,
+ PipTaskOrganizer pipTaskOrganizer,
PipTransitionState pipTransitionState,
- PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+ PipTouchHandler pipTouchHandler,
+ PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(PipController.create(context, shellController, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
- pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
+ pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ oneHandedController, mainExecutor));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 52f0c42..99922fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -59,9 +59,9 @@
adb shell dumpsys activity service SystemUIService WMShell
```
-If information should be added to the dump, make updates to:
-- `WMShell` if you are dumping SysUI state
-- `ShellCommandHandler` if you are dumping Shell state
+If information should be added to the dump, either:
+- Update `WMShell` if you are dumping SysUI state
+- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
## Debugging in Android Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index 0dd50b1..d6302e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -32,7 +32,7 @@
More detail can be found in [go/wm-sysui-dagger](http://go/wm-sysui-dagger).
-## Interfaces to Shell components
+## Interfaces from SysUI to Shell components
Within the same process, the WM Shell components can be running on a different thread than the main
SysUI thread (disabled on certain products). This introduces challenges where we have to be
@@ -54,12 +54,30 @@
Adding an interface to a Shell component may seem like a lot of boiler plate, but is currently
necessary to maintain proper threading and logic isolation.
-## Configuration changes & other SysUI events
+## Listening for Configuration changes & other SysUI events
-Aside from direct calls into Shell controllers for exposed features, the Shell also receives
+Aside from direct calls into Shell controllers for exposed features, the Shell also receives
common event callbacks from SysUI via the `ShellController`. This includes things like:
- Configuration changes
-- TODO: Shell init
-- TODO: Shell command
-- TODO: Keyguard events
\ No newline at end of file
+- Keyguard events
+- Shell init
+- Shell dumps & commands
+
+For other events which are specific to the Shell feature, then you can add callback methods on
+the Shell feature interface. Any such calls should <u>**never**</u> be synchronous calls as
+they will need to post to the Shell main thread to run.
+
+## Shell commands & Dumps
+
+Since the Shell library is a part of the SysUI process, it relies on SysUI to trigger commands
+on individual Shell components, or to dump individual shell components.
+
+```shell
+# Dump everything
+adb shell dumpsys activity service SystemUIService WMShell
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell help
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 665b035..32125fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -27,7 +27,9 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -38,6 +40,7 @@
private static final String TAG = "HideDisplayCutoutController";
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final HideDisplayCutoutOrganizer mOrganizer;
@VisibleForTesting
@@ -49,7 +52,10 @@
*/
@Nullable
public static HideDisplayCutoutController create(Context context,
- ShellController shellController, DisplayController displayController,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
ShellExecutor mainExecutor) {
// The SystemProperty is set for devices that support this feature and is used to control
// whether to create the HideDisplayCutout instance.
@@ -60,14 +66,24 @@
HideDisplayCutoutOrganizer organizer =
new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
- return new HideDisplayCutoutController(context, shellController, organizer);
+ return new HideDisplayCutoutController(context, shellInit, shellCommandHandler,
+ shellController, organizer);
}
- HideDisplayCutoutController(Context context, ShellController shellController,
+ HideDisplayCutoutController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
HideDisplayCutoutOrganizer organizer) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mOrganizer = organizer;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
updateStatus();
mShellController.addConfigurationChangeListener(this);
}
@@ -96,11 +112,11 @@
updateStatus();
}
- public void dump(@NonNull PrintWriter pw) {
- final String prefix = " ";
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = " ";
pw.print(TAG);
pw.println(" states: ");
- pw.print(prefix);
+ pw.print(innerPrefix);
pw.print("mEnabled=");
pw.println(mEnabled);
mOrganizer.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 73b9b89..2fdd121 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -50,6 +50,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -73,6 +74,7 @@
private final Handler mMainHandler;
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final SyncTransactionQueue mSyncQueue;
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
@@ -141,6 +143,8 @@
@VisibleForTesting
KidsModeTaskOrganizer(
Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
@@ -150,19 +154,23 @@
KidsModeSettingsObserver kidsModeSettingsObserver,
ShellExecutor mainExecutor,
Handler mainHandler) {
- super(/* shellInit= */ null, taskOrganizerController, /* compatUI= */ null,
- unfoldAnimationController, recentTasks, mainExecutor);
+ // Note: we don't call super with the shell init because we will be initializing manually
+ super(/* shellInit= */ null, /* shellCommandHandler= */ null, taskOrganizerController,
+ /* compatUI= */ null, unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mKidsModeSettingsObserver = kidsModeSettingsObserver;
+ shellInit.addInitCallback(this::onInit, this);
}
public KidsModeTaskOrganizer(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -171,9 +179,10 @@
ShellExecutor mainExecutor,
Handler mainHandler) {
// Note: we don't call super with the shell init because we will be initializing manually
- super(/* shellInit= */ null, /* compatUI= */ null, unfoldAnimationController, recentTasks,
- mainExecutor);
+ super(/* shellInit= */ null, /* taskOrganizerController= */ null, /* compatUI= */ null,
+ unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
@@ -185,6 +194,9 @@
* Initializes kids mode status.
*/
public void onInit() {
+ if (mShellCommandHandler != null) {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ }
if (mKidsModeSettingsObserver == null) {
mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 24f02ac..9149204 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -56,7 +56,9 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -85,6 +87,7 @@
private Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final AccessibilityManager mAccessibilityManager;
private final DisplayController mDisplayController;
@@ -192,8 +195,9 @@
/**
* Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
- public static OneHandedController create(
- Context context, ShellController shellController, WindowManager windowManager,
+ public static OneHandedController create(Context context,
+ ShellInit shellInit, ShellCommandHandler shellCommandHandler,
+ ShellController shellController, WindowManager windowManager,
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
@@ -213,14 +217,16 @@
context, displayLayout, settingsUtil, animationController, tutorialHandler,
jankMonitor, mainExecutor);
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
- return new OneHandedController(context, shellController, displayController, organizer,
- touchHandler, tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler,
- oneHandedState, oneHandedUiEventsLogger, taskStackListener,
- mainExecutor, mainHandler);
+ return new OneHandedController(context, shellInit, shellCommandHandler, shellController,
+ displayController, organizer, touchHandler, tutorialHandler, settingsUtil,
+ accessibilityUtil, timeoutHandler, oneHandedState, oneHandedUiEventsLogger,
+ taskStackListener, mainExecutor, mainHandler);
}
@VisibleForTesting
OneHandedController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
@@ -235,6 +241,7 @@
ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mOneHandedSettingsUtil = settingsUtil;
mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
@@ -247,8 +254,8 @@
mMainHandler = mainHandler;
mOneHandedUiEventLogger = uiEventsLogger;
mTaskStackListener = taskStackListener;
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1);
final int sysPropPercentageConfig = SystemProperties.getInt(
@@ -268,6 +275,12 @@
getObserver(this::onSwipeToNotificationEnabledChanged);
mShortcutEnabledObserver = getObserver(this::onShortcutEnabledChanged);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
mDisplayController.addDisplayChangingController(this);
setupCallback();
registerSettingObservers(mUserId);
@@ -275,7 +288,6 @@
updateSettings();
updateDisplayLayout(mContext.getDisplayId());
- mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
@@ -623,7 +635,7 @@
updateOneHandedEnabled();
}
- public void dump(@NonNull PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = " ";
pw.println();
pw.println(TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 38631ce..93172f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -20,7 +20,6 @@
import com.android.wm.shell.common.annotations.ExternalThread;
-import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -99,12 +98,4 @@
* view hierarchy or destroyed.
*/
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
-
- /**
- * Dump the current state and information if need.
- *
- * @param pw The stream to dump information to.
- */
- default void dump(PrintWriter pw) {
- }
}
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/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 2144305..fc97f31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -89,7 +89,9 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -122,6 +124,7 @@
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private Optional<OneHandedController> mOneHandedController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
protected final PipImpl mImpl;
@@ -295,6 +298,8 @@
*/
@Nullable
public static Pip create(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
@@ -319,16 +324,18 @@
return null;
}
- return new PipController(context, shellController, displayController, pipAppOpsListener,
- pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController,
+ return new PipController(context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
+ pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
.mImpl;
}
protected PipController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
@@ -355,6 +362,7 @@
}
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
@@ -378,11 +386,11 @@
.getInteger(R.integer.config_pipEnterAnimationDuration);
mPipParamsChangedForwarder = pipParamsChangedForwarder;
- //TODO: move this to ShellInit when PipController can be injected
- mMainExecutor.execute(this::init);
+ shellInit.addInitCallback(this::onInit, this);
}
- public void init() {
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
@@ -628,7 +636,8 @@
mPipTaskOrganizer.scheduleAnimateResizePip(
postChangeBounds, duration, null /* updateBoundsCallback */);
} else {
- mTouchHandler.getMotionHelper().movePip(postChangeBounds);
+ // Directly move PiP to its final destination bounds without animation.
+ mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
}
} else {
updateDisplayLayout.run();
@@ -918,7 +927,7 @@
return true;
}
- private void dump(PrintWriter pw) {
+ private void dump(PrintWriter pw, String prefix) {
final String innerPrefix = " ";
pw.println(TAG);
mMenuController.dump(pw, innerPrefix);
@@ -1006,18 +1015,6 @@
PipController.this.showPictureInPictureMenu();
});
}
-
- @Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> {
- PipController.this.dump(pw);
- });
- } catch (InterruptedException e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to dump PipController in 2s", TAG);
- }
- }
}
/**
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/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 3d1a7e9..7b42350 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -62,6 +63,7 @@
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasks mImpl = new RecentTasksImpl();
@@ -87,20 +89,24 @@
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, taskStackListener, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
+ mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mMainExecutor = mainExecutor;
@@ -112,6 +118,7 @@
}
private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index a372acb..2117b69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -84,6 +84,7 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -131,6 +132,7 @@
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
@@ -147,6 +149,7 @@
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
+ private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
@@ -155,6 +158,7 @@
public SplitScreenController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
@@ -168,6 +172,7 @@
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -183,6 +188,7 @@
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
@@ -200,6 +206,9 @@
*/
@VisibleForTesting
void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
+ this);
mShellController.addKeyguardChangeListener(this);
if (mStageCoordinator == null) {
// TODO: Multi-display
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
new file mode 100644
index 0000000..681d964
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.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.wm.shell.splitscreen;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+import com.android.wm.shell.sysui.ShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Handles the shell commands for the SplitscreenController.
+ */
+public class SplitScreenShellCommandHandler implements
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ private final SplitScreenController mController;
+
+ public SplitScreenShellCommandHandler(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ @Override
+ public boolean onShellCommand(String[] args, PrintWriter pw) {
+ switch (args[0]) {
+ case "moveToSideStage":
+ return runMoveToSideStage(args, pw);
+ case "removeFromSideStage":
+ return runRemoveFromSideStage(args, pw);
+ case "setSideStagePosition":
+ return runSetSideStagePosition(args, pw);
+ default:
+ pw.println("Invalid command: " + args[0]);
+ return false;
+ }
+ }
+
+ private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 3) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[1]);
+ final int sideStagePosition = args.length > 3
+ ? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ mController.moveToSideStage(taskId, sideStagePosition);
+ return true;
+ }
+
+ private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[1]);
+ mController.removeFromSideStage(taskId);
+ return true;
+ }
+
+ private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: side stage position should be provided as arguments");
+ return false;
+ }
+ final int position = new Integer(args[1]);
+ mController.setSideStagePosition(position);
+ return true;
+ }
+
+ @Override
+ public void printShellCommandHelp(PrintWriter pw, String prefix) {
+ pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
+ pw.println(prefix + " Move a task with given id in split-screen mode.");
+ pw.println(prefix + "removeFromSideStage <taskId>");
+ pw.println(prefix + " Remove a task with given id in split-screen mode.");
+ pw.println(prefix + "setSideStagePosition <SideStagePosition>");
+ pw.println(prefix + " Sets the position of the side-stage.");
+ }
+}
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/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index f4fc0c4..2e6ddc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -16,19 +16,14 @@
package com.android.wm.shell.sysui;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
/**
* An entry point into the shell for dumping shell internal state and running adb commands.
@@ -38,52 +33,61 @@
public final class ShellCommandHandler {
private static final String TAG = ShellCommandHandler.class.getSimpleName();
- private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<Pip> mPipOptional;
- private final Optional<OneHandedController> mOneHandedOptional;
- private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
- private final Optional<RecentTasksController> mRecentTasks;
- private final ShellTaskOrganizer mShellTaskOrganizer;
- private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
+ // We're using a TreeMap to keep them sorted by command name
+ private final TreeMap<String, BiConsumer<PrintWriter, String>> mDumpables = new TreeMap<>();
+ private final TreeMap<String, ShellCommandActionHandler> mCommands = new TreeMap<>();
- public ShellCommandHandler(
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<RecentTasksController> recentTasks,
- ShellExecutor mainExecutor) {
- mShellTaskOrganizer = shellTaskOrganizer;
- mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
- mRecentTasks = recentTasks;
- mSplitScreenOptional = splitScreenOptional;
- mPipOptional = pipOptional;
- mOneHandedOptional = oneHandedOptional;
- mHideDisplayCutout = hideDisplayCutout;
- // TODO(238217847): To be removed once the command handler dependencies are inverted
- shellController.setShellCommandHandler(this);
+ public interface ShellCommandActionHandler {
+ /**
+ * Handles the given command.
+ *
+ * @param args the arguments starting with the action name, then the action arguments
+ * @param pw the write to print output to
+ */
+ boolean onShellCommand(String[] args, PrintWriter pw);
+
+ /**
+ * Prints the help for this class of commands. Implementations do not need to print the
+ * command class.
+ */
+ void printShellCommandHelp(PrintWriter pw, String prefix);
+ }
+
+
+ /**
+ * Adds a callback to run when the Shell is being dumped.
+ *
+ * @param callback the callback to be made when Shell is dumped, takes the print writer and
+ * a prefix
+ * @param instance used for debugging only
+ */
+ public <T> void addDumpCallback(BiConsumer<PrintWriter, String> callback, T instance) {
+ mDumpables.put(instance.getClass().getSimpleName(), callback);
+ ProtoLog.v(WM_SHELL_INIT, "Adding dump callback for %s",
+ instance.getClass().getSimpleName());
+ }
+
+ /**
+ * Adds an action callback to be invoked when the user runs that particular command from adb.
+ *
+ * @param commandClass the top level class of command to invoke
+ * @param actions the interface to callback when an action of this class is invoked
+ * @param instance used for debugging only
+ */
+ public <T> void addCommandCallback(String commandClass, ShellCommandActionHandler actions,
+ T instance) {
+ mCommands.put(commandClass, actions);
+ ProtoLog.v(WM_SHELL_INIT, "Adding command class %s for %s", commandClass,
+ instance.getClass().getSimpleName());
}
/** Dumps WM Shell internal state. */
public void dump(PrintWriter pw) {
- mShellTaskOrganizer.dump(pw, "");
- pw.println();
- pw.println();
- mPipOptional.ifPresent(pip -> pip.dump(pw));
- mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
- mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
- pw.println();
- pw.println();
- mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, ""));
- pw.println();
- pw.println();
- mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, ""));
- pw.println();
- pw.println();
- mKidsModeTaskOrganizer.dump(pw, "");
+ for (String key : mDumpables.keySet()) {
+ final BiConsumer<PrintWriter, String> r = mDumpables.get(key);
+ r.accept(pw, "");
+ pw.println();
+ }
}
@@ -93,72 +97,32 @@
// Argument at position 0 is "WMShell".
return false;
}
- switch (args[1]) {
- case "moveToSideStage":
- return runMoveToSideStage(args, pw);
- case "removeFromSideStage":
- return runRemoveFromSideStage(args, pw);
- case "setSideStagePosition":
- return runSetSideStagePosition(args, pw);
- case "help":
- return runHelp(pw);
- default:
- return false;
- }
- }
- private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as arguments");
+ final String cmdClass = args[1];
+ if (cmdClass.toLowerCase().equals("help")) {
+ return runHelp(pw);
+ }
+ if (!mCommands.containsKey(cmdClass)) {
return false;
}
- final int taskId = new Integer(args[2]);
- final int sideStagePosition = args.length > 3
- ? new Integer(args[3]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
- mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
- return true;
- }
- private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as arguments");
- return false;
- }
- final int taskId = new Integer(args[2]);
- mSplitScreenOptional.ifPresent(split -> split.removeFromSideStage(taskId));
- return true;
- }
-
- private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: side stage position should be provided as arguments");
- return false;
- }
- final int position = new Integer(args[2]);
- mSplitScreenOptional.ifPresent(split -> split.setSideStagePosition(position));
+ // Only pass the actions onwards as arguments to the callback
+ final ShellCommandActionHandler actions = mCommands.get(args[1]);
+ final String[] cmdClassArgs = Arrays.copyOfRange(args, 2, args.length);
+ actions.onShellCommand(cmdClassArgs, pw);
return true;
}
private boolean runHelp(PrintWriter pw) {
pw.println("Window Manager Shell commands:");
+ for (String commandClass : mCommands.keySet()) {
+ pw.println(" " + commandClass);
+ mCommands.get(commandClass).printShellCommandHelp(pw, " ");
+ }
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" <no arguments provided>");
- pw.println(" Dump Window Manager Shell internal state");
- pw.println(" pair <taskId1> <taskId2>");
- pw.println(" unpair <taskId>");
- pw.println(" Pairs/unpairs tasks with given ids.");
- pw.println(" moveToSideStage <taskId> <SideStagePosition>");
- pw.println(" Move a task with given id in split-screen mode.");
- pw.println(" removeFromSideStage <taskId>");
- pw.println(" Remove a task with given id in split-screen mode.");
- pw.println(" setSideStageOutline <true/false>");
- pw.println(" Enable/Disable outline on the side-stage.");
- pw.println(" setSideStagePosition <SideStagePosition>");
- pw.println(" Sets the position of the side-stage.");
+ pw.println(" Dump all Window Manager Shell internal state");
return true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index f1f317f..52ffb46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -45,11 +45,10 @@
private static final String TAG = ShellController.class.getSimpleName();
private final ShellInit mShellInit;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
- private ShellCommandHandler mShellCommandHandler;
-
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
@@ -57,8 +56,10 @@
private Configuration mLastConfiguration;
- public ShellController(ShellInit shellInit, ShellExecutor mainExecutor) {
+ public ShellController(ShellInit shellInit, ShellCommandHandler shellCommandHandler,
+ ShellExecutor mainExecutor) {
mShellInit = shellInit;
+ mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
}
@@ -70,15 +71,6 @@
}
/**
- * Sets the command handler to call back to.
- * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
- * command handler to other classes.
- */
- public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) {
- mShellCommandHandler = shellCommandHandler;
- }
-
- /**
* Adds a new configuration listener. The configuration change callbacks are not made in any
* particular order.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index c250e03..ac52235 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -52,6 +52,9 @@
* Adds a callback to the ordered list of callbacks be made when Shell is first started. This
* can be used in class constructors when dagger is used to ensure that the initialization order
* matches the dependency order.
+ *
+ * @param r the callback to be made when Shell is initialized
+ * @param instance used for debugging only
*/
public <T extends Object> void addInitCallback(Runnable r, T instance) {
if (mHasInitialized) {
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/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7517e8a..f865649 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -59,6 +59,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -85,10 +86,12 @@
@Mock
private CompatUIController mCompatUI;
@Mock
- private ShellInit mShellInit;
+ private ShellExecutor mTestExecutor;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
- ShellTaskOrganizer mOrganizer;
- private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
+ private ShellTaskOrganizer mOrganizer;
+ private ShellInit mShellInit;
private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
@@ -132,8 +135,11 @@
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
- mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mTaskOrganizerController,
- mCompatUI, Optional.empty(), Optional.empty(), mTestExecutor));
+ mShellInit = spy(new ShellInit(mTestExecutor));
+ mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+ mTaskOrganizerController, mCompatUI, Optional.empty(), Optional.empty(),
+ mTestExecutor));
+ mShellInit.init();
}
@Test
@@ -142,9 +148,12 @@
}
@Test
- public void testRegisterOrganizer_sendRegisterTaskOrganizer() throws RemoteException {
- mOrganizer.registerOrganizer();
+ public void instantiate_addDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+ @Test
+ public void testInit_sendRegisterTaskOrganizer() throws RemoteException {
verify(mTaskOrganizerController).registerTaskOrganizer(any(ITaskOrganizer.class));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index ba81602..5b3b8fd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -61,6 +62,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Ignore;
@@ -81,6 +83,7 @@
private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
+ private ShellInit mShellInit;
@Rule
public TestableContext mContext =
@@ -110,10 +113,12 @@
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION,
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
- mController = new BackAnimationController(
+ mShellInit = spy(new ShellInit(mShellExecutor));
+ mController = new BackAnimationController(mShellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ mShellInit.init();
mEventTime = 0;
mShellExecutor.flushAll();
}
@@ -160,6 +165,11 @@
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
@@ -233,10 +243,12 @@
public void animationDisabledFromSettings() throws RemoteException {
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
- mController = new BackAnimationController(
+ ShellInit shellInit = new ShellInit(mShellExecutor);
+ mController = new BackAnimationController(shellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ shellInit.init();
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
@@ -272,9 +284,14 @@
// the previous transition is finished.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
verifyNoMoreInteractions(mIOnBackInvokedCallback);
+ mController.onBackToLauncherAnimationFinished();
+
+ // Verify that more events from a rejected swipe cannot start animation.
+ doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ doMotionEvent(MotionEvent.ACTION_UP, 0);
+ verifyNoMoreInteractions(mIOnBackInvokedCallback);
// Verify that we start accepting gestures again once transition finishes.
- mController.onBackToLauncherAnimationFinished();
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
verify(mIOnBackInvokedCallback).onBackStarted();
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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 828c13e..6292130 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -54,6 +55,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -79,6 +81,7 @@
private static final int TASK_ID = 12;
private CompatUIController mController;
+ private ShellInit mShellInit;
private @Mock ShellController mMockShellController;
private @Mock DisplayController mMockDisplayController;
private @Mock DisplayInsetsController mMockDisplayInsetsController;
@@ -107,9 +110,10 @@
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
- mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController,
- mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
- mMockTransitionsLazy) {
+ mShellInit = spy(new ShellInit(mMockExecutor));
+ mController = new CompatUIController(mContext, mShellInit, mMockShellController,
+ mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
+ mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -122,10 +126,16 @@
return mMockLetterboxEduLayout;
}
};
+ mShellInit.init();
spyOn(mController);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void instantiateController_registerKeyguardChangeListener() {
verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index dcc504a..6c301bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.hidedisplaycutout;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -29,7 +31,10 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -45,17 +50,32 @@
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
private ShellController mShellController;
@Mock
private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
private HideDisplayCutoutController mHideDisplayCutoutController;
+ private ShellInit mShellInit;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mHideDisplayCutoutController = new HideDisplayCutoutController(
- mContext, mShellController, mMockDisplayAreaOrganizer);
+ mShellInit = spy(new ShellInit(mock(ShellExecutor.class)));
+ mHideDisplayCutoutController = new HideDisplayCutoutController(mContext, mShellInit,
+ mShellCommandHandler, mShellController, mMockDisplayAreaOrganizer);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index a919ad0..ecfb427 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -49,6 +49,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -74,6 +75,7 @@
@Mock private WindowContainerTransaction mTransaction;
@Mock private KidsModeSettingsObserver mObserver;
@Mock private ShellInit mShellInit;
+ @Mock private ShellCommandHandler mShellCommandHandler;
@Mock private DisplayInsetsController mDisplayInsetsController;
KidsModeTaskOrganizer mOrganizer;
@@ -87,14 +89,20 @@
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mTaskOrganizerController,
- mSyncTransactionQueue, mDisplayController, mDisplayInsetsController,
- Optional.empty(), Optional.empty(), mObserver, mTestExecutor, mHandler));
+ mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
+ mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
+ mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
+ mTestExecutor, mHandler));
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void testKidsModeOn() {
doReturn(true).when(mObserver).isEnabled();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index dbf93ae..90645ce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -36,7 +36,6 @@
import android.graphics.Rect;
import android.os.Handler;
-import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.util.ArrayMap;
import android.view.Display;
@@ -49,7 +48,9 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -61,18 +62,20 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class OneHandedControllerTest extends OneHandedTestCase {
- private int mCurrentUser = UserHandle.myUserId();
Display mDisplay;
OneHandedAccessibilityUtil mOneHandedAccessibilityUtil;
OneHandedController mSpiedOneHandedController;
OneHandedTimeoutHandler mSpiedTimeoutHandler;
OneHandedState mSpiedTransitionState;
+ ShellInit mShellInit;
@Mock
+ ShellCommandHandler mMockShellCommandHandler;
+ @Mock
ShellController mMockShellController;
@Mock
- DisplayLayout mDisplayLayout;
+ DisplayLayout mMockDisplayLayout;
@Mock
DisplayController mMockDisplayController;
@Mock
@@ -102,7 +105,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mDisplay = mContext.getDisplay();
- mDisplayLayout = Mockito.mock(DisplayLayout.class);
+ mMockDisplayLayout = Mockito.mock(DisplayLayout.class);
mSpiedTimeoutHandler = spy(new OneHandedTimeoutHandler(mMockShellMainExecutor));
mSpiedTransitionState = spy(new OneHandedState());
@@ -122,11 +125,14 @@
when(mMockDisplayAreaOrganizer.getLastDisplayBounds()).thenReturn(
new Rect(0, 0, 1080, 2400));
- when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mDisplayLayout);
+ when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mMockDisplayLayout);
+ mShellInit = spy(new ShellInit(mMockShellMainExecutor));
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mShellInit,
+ mMockShellCommandHandler,
mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
@@ -141,6 +147,17 @@
mMockShellMainExecutor,
mMockShellMainHandler)
);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
}
@Test
@@ -308,9 +325,9 @@
@Test
public void testRotation90CanNotStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedTransitionState.setState(STATE_NONE);
- when(mDisplayLayout.isLandscape()).thenReturn(true);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(true);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
@@ -320,10 +337,10 @@
@Test
public void testRotation180CanStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
mSpiedTransitionState.setState(STATE_NONE);
when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true);
- when(mDisplayLayout.isLandscape()).thenReturn(false);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(false);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
@@ -333,9 +350,9 @@
@Test
public void testRotation270CanNotStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mSpiedTransitionState.setState(STATE_NONE);
- when(mDisplayLayout.isLandscape()).thenReturn(true);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(true);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index e6a8220..a39bdf0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -41,7 +41,9 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +63,10 @@
OneHandedState mSpiedState;
@Mock
+ ShellInit mMockShellInit;
+ @Mock
+ ShellCommandHandler mMockShellCommandHandler;
+ @Mock
ShellController mMockShellController;
@Mock
DisplayController mMockDisplayController;
@@ -111,6 +117,8 @@
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mMockShellInit,
+ mMockShellCommandHandler,
mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index f192514..9ed8d84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,7 +55,9 @@
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -74,7 +76,9 @@
@TestableLooper.RunWithLooper
public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
+ private ShellInit mShellInit;
+ @Mock private ShellCommandHandler mMockShellCommandHandler;
@Mock private ShellController mMockShellController;
@Mock private DisplayController mMockDisplayController;
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@@ -105,19 +109,31 @@
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mMockExecutor).execute(any());
- mPipController = new PipController(mContext, mMockShellController, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipKeepClearAlgorithm,
+ mShellInit = spy(new ShellInit(mMockExecutor));
+ mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
+ mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
mMockOneHandedController, mMockExecutor);
+ mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
}
@Test
+ public void instantiatePipController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+
+ @Test
public void instantiatePipController_registerConfigChangeListener() {
verify(mMockShellController, times(1)).addConfigurationChangeListener(any());
}
@@ -149,9 +165,10 @@
when(mockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
- assertNull(PipController.create(spyContext, mMockShellController, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipKeepClearAlgorithm,
+ ShellInit shellInit = new ShellInit(mMockExecutor);
+ assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
+ mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -217,7 +234,7 @@
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
- verify(mMockPipMotionHelper).movePip(any(Rect.class));
+ verify(mMockPipTaskOrganizer).scheduleFinishResizePip(any(Rect.class));
}
@Test
@@ -233,7 +250,7 @@
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
- verify(mMockPipMotionHelper, never()).movePip(any(Rect.class));
+ verify(mMockPipTaskOrganizer, never()).scheduleFinishResizePip(any(Rect.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index d406a4e..81bb609 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -23,7 +23,9 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -48,6 +50,7 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -73,21 +76,35 @@
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
- private ShellInit mShellInit;
+ private ShellCommandHandler mShellCommandHandler;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
+ private ShellInit mShellInit;
private ShellExecutor mMainExecutor;
@Before
public void setUp() {
mMainExecutor = new TestShellExecutor();
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mTaskStackListener, mMainExecutor));
- mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit,
+ mShellCommandHandler, mTaskStackListener, mMainExecutor));
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
mMainExecutor);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), isA(RecentTasksController.class));
+ }
+
+ @Test
+ public void instantiateController_addDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(),
+ isA(RecentTasksController.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index c7c78d3..5a68361 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -54,6 +55,7 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -73,8 +75,9 @@
@RunWith(AndroidJUnit4.class)
public class SplitScreenControllerTests extends ShellTestCase {
- @Mock ShellController mShellController;
@Mock ShellInit mShellInit;
+ @Mock ShellController mShellController;
+ @Mock ShellCommandHandler mShellCommandHandler;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock SyncTransactionQueue mSyncQueue;
@Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -94,9 +97,10 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
- mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController,
- mDisplayImeController, mDisplayInsetsController, mDragAndDropController,
- mTransitions, mTransactionPool, mIconProvider, mRecentTasks, mMainExecutor));
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mMainExecutor));
}
@Test
@@ -105,7 +109,24 @@
}
@Test
+ public void instantiateController_registerDumpCallback() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerCommandCallback() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellCommandHandler, times(1)).addCommandCallback(eq("splitscreen"), any(), any());
+ }
+
+ @Test
public void testControllerRegistersKeyguardChangeListener() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
mSplitScreenController.onInit();
verify(mShellController, times(1)).addKeyguardChangeListener(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 02311ba..39e58ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -45,6 +45,8 @@
@Mock
private ShellInit mShellInit;
@Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
private ShellExecutor mExecutor;
private ShellController mController;
@@ -56,7 +58,7 @@
MockitoAnnotations.initMocks(this);
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
- mController = new ShellController(mShellInit, mExecutor);
+ mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 0ef80ee..132234b 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -407,14 +407,28 @@
indices = (const uint16_t*)(indexA.ptr() + indexIndex);
}
- SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle);
+ SkVertices::VertexMode vertexMode = static_cast<SkVertices::VertexMode>(modeHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount,
- reinterpret_cast<const SkPoint*>(verts),
- reinterpret_cast<const SkPoint*>(texs),
- reinterpret_cast<const SkColor*>(colors),
- indexCount, indices).get(),
- SkBlendMode::kModulate, *paint);
+
+ // Preserve legacy Skia behavior: ignore the shader if there are no texs set.
+ Paint noShaderPaint;
+ if (jtexs == NULL) {
+ noShaderPaint = Paint(*paint);
+ noShaderPaint.setShader(nullptr);
+ paint = &noShaderPaint;
+ }
+ // Since https://skia-review.googlesource.com/c/skia/+/473676, Skia will blend paint and vertex
+ // colors when no shader is provided. This ternary uses kDst to mimic the old behavior of
+ // ignoring the paint and using the vertex colors directly when no shader is provided.
+ SkBlendMode blendMode = paint->getShader() ? SkBlendMode::kModulate : SkBlendMode::kDst;
+
+ get_canvas(canvasHandle)
+ ->drawVertices(SkVertices::MakeCopy(
+ vertexMode, vertexCount, reinterpret_cast<const SkPoint*>(verts),
+ reinterpret_cast<const SkPoint*>(texs),
+ reinterpret_cast<const SkColor*>(colors), indexCount, indices)
+ .get(),
+ blendMode, *paint);
}
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 01e3de2..898935f 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -35,7 +35,7 @@
<!-- need to keep this outer view in order to have a correctly sized anchor
for the dropdown menu, as well as dropdown background in the right place -->
- <LinearLayout
+ <com.android.keyguard.KeyguardUserSwitcherAnchor
android:id="@+id/user_switcher_anchor"
android:orientation="horizontal"
android:layout_height="wrap_content"
@@ -48,7 +48,7 @@
android:textDirection="locale"
android:layout_width="@dimen/bouncer_user_switcher_width"
android:layout_height="wrap_content" />
- </LinearLayout>>
+ </com.android.keyguard.KeyguardUserSwitcherAnchor>
</LinearLayout>
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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9c2542c..1bf3037 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -887,7 +887,8 @@
<!-- Accessibility label for the button that opens the user switcher. -->
<string name="accessibility_multi_user_switch_switcher">Switch user</string>
- <!-- Accessibility label for the button that opens the user switcher and announces the current user. -->
+ <!-- Accessibility role description for the element that opens the user switcher list. -->
+ <string name="accessibility_multi_user_list_switcher">pulldown menu</string>
<!-- Accessibility label for the user icon on the lock screen. -->
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index ee0c4fb6..a82684d03 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -43,7 +43,7 @@
android:id="@+id/date">
<Layout
android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintEnd_toStartOf="@id/barrier"
@@ -61,7 +61,7 @@
app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
- app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
/>
@@ -76,7 +76,7 @@
app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
- app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
/>
@@ -100,8 +100,8 @@
android:layout_height="0dp"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toEndOf="@id/end_guide"
- app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="@id/date"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
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/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 0149751..4613e8b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -100,4 +100,14 @@
* Sent when the desired dark intensity of the nav buttons has changed
*/
void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
+
+ /**
+ * Sent when screen started turning on.
+ */
+ void onScreenTurningOn() = 23;
+
+ /**
+ * Sent when screen started turning off.
+ */
+ void onScreenTurningOff() = 24;
}
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/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f697e25..3517d22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1129,11 +1129,13 @@
Log.e(TAG, "Current user in user switcher is null.");
return;
}
+ final String currentUserName = mUserSwitcherController.getCurrentUserName();
Drawable userIcon = findUserIcon(currentUser.info.id);
((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon);
- mUserSwitcher.setText(mUserSwitcherController.getCurrentUserName());
+ mUserSwitcher.setText(currentUserName);
- ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor);
+ KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
+
BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
@@ -1213,7 +1215,6 @@
anchor.setOnClickListener((v) -> {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
-
mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
mPopup.setAnchorView(anchor);
mPopup.setAdapter(adapter);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 89d6fb5..acbea1b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -29,7 +29,7 @@
/**
* Translates items away/towards the hinge when the device is opened/closed. This is controlled by
- * the set of ids, which also dictact which direction to move and when, via a filter function.
+ * the set of ids, which also dictate which direction to move and when, via a filter function.
*/
@SysUIUnfoldScope
class KeyguardUnfoldTransition
@@ -55,7 +55,9 @@
ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
ViewIdToTranslate(
R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)),
+ ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever),
+ ViewIdToTranslate(R.id.start_button, LEFT, filterNever),
+ ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)),
progressProvider = unfoldProgressProvider)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt
new file mode 100644
index 0000000..5f3ba72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.keyguard
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.LinearLayout
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.android.systemui.R
+
+/**
+ * Custom View for the multi-user switcher pull-down menu anchor
+ */
+class KeyguardUserSwitcherAnchor @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+
+ override fun createAccessibilityNodeInfo(): AccessibilityNodeInfo {
+ val info = super.createAccessibilityNodeInfo()
+ AccessibilityNodeInfoCompat.wrap(info).roleDescription =
+ context.getString(R.string.accessibility_multi_user_list_switcher)
+ return info
+ }
+}
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/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 044a57c..0a55294 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -41,4 +41,12 @@
override fun onScreenTurnedOn() {
listeners.forEach(ScreenListener::onScreenTurnedOn)
}
+
+ override fun onScreenTurningOff() {
+ listeners.forEach(ScreenListener::onScreenTurningOff)
+ }
+
+ override fun onScreenTurningOn(ignored: Runnable) {
+ listeners.forEach(ScreenListener::onScreenTurningOn)
+ }
}
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/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 77652c9..ac46c85 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -29,6 +29,7 @@
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.UiEventLogger;
@@ -47,6 +48,7 @@
import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -88,6 +90,10 @@
public static final int POSITION_AT_END = -1;
public static final String TILES_SETTING = Secure.QS_TILES;
+ // Shared prefs that hold tile lifecycle info.
+ @VisibleForTesting
+ static final String TILES = "tiles_prefs";
+
private final Context mContext;
private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
protected final ArrayList<String> mTileSpecs = new ArrayList<>();
@@ -99,6 +105,7 @@
private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister;
private final Executor mMainExecutor;
+ private final UserFileManager mUserFileManager;
private final List<Callback> mCallbacks = new ArrayList<>();
@Nullable
@@ -135,7 +142,8 @@
SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
- TileLifecycleManager.Factory tileLifecycleManagerFactory
+ TileLifecycleManager.Factory tileLifecycleManagerFactory,
+ UserFileManager userFileManager
) {
mIconController = iconController;
mContext = context;
@@ -148,6 +156,7 @@
mMainExecutor = mainExecutor;
mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
+ mUserFileManager = userFileManager;
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
mCentralSurfacesOptional = centralSurfacesOptional;
@@ -392,6 +401,11 @@
*/
@Override
public void removeTile(String spec) {
+ if (spec.startsWith(CustomTile.PREFIX)) {
+ // If the tile is removed (due to it not actually existing), mark it as removed. That
+ // way it will be marked as newly added if it appears in the future.
+ setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false);
+ }
mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
}
@@ -515,7 +529,7 @@
lifecycleManager.onStopListening();
lifecycleManager.onTileRemoved();
mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
- TileLifecycleManager.setTileAdded(mContext, component, false);
+ setTileAdded(component, mCurrentUser, false);
lifecycleManager.flushMessagesAndUnbind();
}
}
@@ -552,6 +566,36 @@
throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
}
+ /**
+ * Check if a particular {@link CustomTile} has been added for a user and has not been removed
+ * since.
+ * @param componentName the {@link ComponentName} of the
+ * {@link android.service.quicksettings.TileService} associated with the
+ * tile.
+ * @param userId the user to check
+ */
+ public boolean isTileAdded(ComponentName componentName, int userId) {
+ return mUserFileManager
+ .getSharedPreferences(TILES, 0, userId)
+ .getBoolean(componentName.flattenToString(), false);
+ }
+
+ /**
+ * Persists whether a particular {@link CustomTile} has been added and it's currently in the
+ * set of selected tiles ({@link #mTiles}.
+ * @param componentName the {@link ComponentName} of the
+ * {@link android.service.quicksettings.TileService} associated
+ * with the tile.
+ * @param userId the user for this tile
+ * @param added {@code true} if the tile is being added, {@code false} otherwise
+ */
+ public void setTileAdded(ComponentName componentName, int userId, boolean added) {
+ mUserFileManager.getSharedPreferences(TILES, 0, userId)
+ .edit()
+ .putBoolean(componentName.flattenToString(), added)
+ .apply();
+ }
+
protected static List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index a49d3fd..3e445dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -127,6 +127,10 @@
TileLifecycleManager create(Intent intent, UserHandle userHandle);
}
+ public int getUserId() {
+ return mUser.getIdentifier();
+ }
+
public ComponentName getComponent() {
return mIntent.getComponent();
}
@@ -507,13 +511,4 @@
public interface TileChangeListener {
void onTileChanged(ComponentName tile);
}
-
- public static boolean isTileAdded(Context context, ComponentName component) {
- return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
- }
-
- public static void setTileAdded(Context context, ComponentName component, boolean added) {
- context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
- added).commit();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index cfc57db..e86bd7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -109,9 +109,9 @@
void startLifecycleManagerAndAddTile() {
mStarted = true;
ComponentName component = mStateManager.getComponent();
- Context context = mServices.getContext();
- if (!TileLifecycleManager.isTileAdded(context, component)) {
- TileLifecycleManager.setTileAdded(context, component, true);
+ final int userId = mStateManager.getUserId();
+ if (!mServices.getHost().isTileAdded(component, userId)) {
+ mServices.getHost().setTileAdded(component, userId, true);
mStateManager.onTileAdded();
mStateManager.flushMessagesAndUnbind();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 438236d..30862b7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -638,12 +638,7 @@
// Listen for user setup
startTracking();
- screenLifecycle.addObserver(new ScreenLifecycle.Observer() {
- @Override
- public void onScreenTurnedOn() {
- notifyScreenTurnedOn();
- }
- });
+ screenLifecycle.addObserver(mLifecycleObserver);
// Connect to the service
updateEnabledState();
@@ -951,20 +946,55 @@
}
}
- /**
- * Notifies the Launcher that screen turned on and ready to use
- */
- public void notifyScreenTurnedOn() {
- try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onScreenTurnedOn();
- } else {
- Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event.");
+ private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() {
+ /**
+ * Notifies the Launcher that screen turned on and ready to use
+ */
+ @Override
+ public void onScreenTurnedOn() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurnedOn();
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e);
}
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call notifyScreenTurnedOn()", e);
}
- }
+
+ /**
+ * Notifies the Launcher that screen is starting to turn on.
+ */
+ @Override
+ public void onScreenTurningOff() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurningOff();
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for screen turning off event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e);
+ }
+ }
+
+ /**
+ * Notifies the Launcher that screen is starting to turn on.
+ */
+ @Override
+ public void onScreenTurningOn(@NonNull Runnable ignored) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurningOn();
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for screen turning on event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e);
+ }
+ }
+ };
void notifyToggleRecentApps() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
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/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/keyguard/KeyguardUserSwitcherAnchorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
new file mode 100644
index 0000000..08185af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.keyguard
+
+import android.testing.AndroidTestingRunner
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class KeyguardUserSwitcherAnchorTest : SysuiTestCase() {
+
+ private lateinit var keyguardUserSwitcherAnchor: KeyguardUserSwitcherAnchor
+
+ @Before
+ fun setUp() {
+ keyguardUserSwitcherAnchor = KeyguardUserSwitcherAnchor(context)
+ }
+
+ @Test
+ fun roleDescription_is_set_to_pulldown_menu() {
+ // GIVEN
+ val roleDescriptionString =
+ context.getString(R.string.accessibility_multi_user_list_switcher)
+
+ // WHEN
+ val result = keyguardUserSwitcherAnchor.createAccessibilityNodeInfo()
+
+ // THEN
+ assertThat(
+ AccessibilityNodeInfoCompat.wrap(result).roleDescription
+ ).isEqualTo(roleDescriptionString)
+ }
+}
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/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 7dbc561..3c58b6fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -22,6 +22,7 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
@@ -32,11 +33,13 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
+import android.util.SparseArray;
import android.view.View;
import androidx.annotation.Nullable;
@@ -60,12 +63,14 @@
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.FakeSharedPreferences;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -76,6 +81,7 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -130,6 +136,10 @@
private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock
private TileLifecycleManager mTileLifecycleManager;
+ @Mock
+ private UserFileManager mUserFileManager;
+
+ private SparseArray<SharedPreferences> mSharedPreferencesByUser;
private FakeExecutor mMainExecutor;
@@ -140,17 +150,29 @@
MockitoAnnotations.initMocks(this);
mMainExecutor = new FakeExecutor(new FakeSystemClock());
+ mSharedPreferencesByUser = new SparseArray<>();
+
when(mTileServiceRequestControllerBuilder.create(any()))
.thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
+ when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+ .thenAnswer((Answer<SharedPreferences>) invocation -> {
+ assertEquals(QSTileHost.TILES, invocation.getArgument(0));
+ int userId = invocation.getArgument(2);
+ if (!mSharedPreferencesByUser.contains(userId)) {
+ mSharedPreferencesByUser.put(userId, new FakeSharedPreferences());
+ }
+ return mSharedPreferencesByUser.get(userId);
+ });
mSecureSettings = new FakeSettings();
saveSetting("");
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
- mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory);
+ mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
+ mUserFileManager);
mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@Override
@@ -528,6 +550,118 @@
assertEquals("spec1", getSetting());
}
+ @Test
+ public void testIsTileAdded_true() {
+ int user = mUserTracker.getUserId();
+ getSharedPreferenecesForUser(user)
+ .edit()
+ .putBoolean(CUSTOM_TILE.flattenToString(), true)
+ .apply();
+
+ assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+ }
+
+ @Test
+ public void testIsTileAdded_false() {
+ int user = mUserTracker.getUserId();
+ getSharedPreferenecesForUser(user)
+ .edit()
+ .putBoolean(CUSTOM_TILE.flattenToString(), false)
+ .apply();
+
+ assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+ }
+
+ @Test
+ public void testIsTileAdded_notSet() {
+ int user = mUserTracker.getUserId();
+
+ assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+ }
+
+ @Test
+ public void testIsTileAdded_differentUser() {
+ int user = mUserTracker.getUserId();
+ mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user)
+ .edit()
+ .putBoolean(CUSTOM_TILE.flattenToString(), true)
+ .apply();
+
+ assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1));
+ }
+
+ @Test
+ public void testSetTileAdded_true() {
+ int user = mUserTracker.getUserId();
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ assertTrue(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileAdded_false() {
+ int user = mUserTracker.getUserId();
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
+
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileAdded_differentUser() {
+ int user = mUserTracker.getUserId();
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ assertFalse(getSharedPreferenecesForUser(user + 1)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileRemoved_afterCustomTileChangedByUser() {
+ int user = mUserTracker.getUserId();
+ saveSetting(CUSTOM_TILE_SPEC);
+
+ // This will be done by TileServiceManager
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ mQSTileHost.changeTilesByUser(mQSTileHost.mTileSpecs, List.of("spec1"));
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileRemoved_removedByUser() {
+ int user = mUserTracker.getUserId();
+ saveSetting(CUSTOM_TILE_SPEC);
+
+ // This will be done by TileServiceManager
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ mQSTileHost.removeTileByUser(CUSTOM_TILE);
+ mMainExecutor.runAllReady();
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileRemoved_removedBySystem() {
+ int user = mUserTracker.getUserId();
+ saveSetting("spec1" + CUSTOM_TILE_SPEC);
+
+ // This will be done by TileServiceManager
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
+ mMainExecutor.runAllReady();
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ private SharedPreferences getSharedPreferenecesForUser(int user) {
+ return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactory defaultFactory, Executor mainExecutor,
@@ -537,11 +671,13 @@
UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
- TileLifecycleManager.Factory tileLifecycleManagerFactory) {
+ TileLifecycleManager.Factory tileLifecycleManagerFactory,
+ UserFileManager userFileManager) {
super(context, iconController, defaultFactory, mainExecutor, pluginManager,
tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
uiEventLogger, userTracker, secureSettings, customTileStatePersister,
- tileServiceRequestControllerBuilder, tileLifecycleManagerFactory);
+ tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
+ userFileManager);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 573980d..8aa625a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -19,6 +19,14 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -31,6 +39,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.UserTracker;
import org.junit.After;
@@ -38,37 +47,45 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileServiceManagerTest extends SysuiTestCase {
+ @Mock
private TileServices mTileServices;
+ @Mock
private TileLifecycleManager mTileLifecycle;
+ @Mock
+ private UserTracker mUserTracker;
+ @Mock
+ private QSTileHost mQSTileHost;
+ @Mock
+ private Context mMockContext;
+
private HandlerThread mThread;
private Handler mHandler;
private TileServiceManager mTileServiceManager;
- private UserTracker mUserTracker;
- private Context mMockContext;
+ private ComponentName mComponentName;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mThread = new HandlerThread("TestThread");
mThread.start();
mHandler = Handler.createAsync(mThread.getLooper());
- mTileServices = Mockito.mock(TileServices.class);
- mUserTracker = Mockito.mock(UserTracker.class);
- Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
- Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
- mMockContext = Mockito.mock(Context.class);
- Mockito.when(mTileServices.getContext()).thenReturn(mMockContext);
- mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
- Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false);
- ComponentName componentName = new ComponentName(mContext,
- TileServiceManagerTest.class);
- Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
+ when(mTileServices.getContext()).thenReturn(mMockContext);
+ when(mTileServices.getHost()).thenReturn(mQSTileHost);
+ when(mTileLifecycle.getUserId()).thenAnswer(invocation -> mUserTracker.getUserId());
+ when(mTileLifecycle.isActiveTile()).thenReturn(false);
+
+ mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
+ when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
mTileLifecycle);
}
@@ -80,17 +97,44 @@
}
@Test
+ public void testSetTileAddedIfNotAdded() {
+ when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+
+ verify(mQSTileHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
+ }
+
+ @Test
+ public void testNotSetTileAddedIfAdded() {
+ when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+
+ verify(mQSTileHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
+ }
+
+ @Test
+ public void testSetTileAddedCorrectUser() {
+ int user = 10;
+ when(mUserTracker.getUserId()).thenReturn(user);
+ when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+
+ verify(mQSTileHost).setTileAdded(mComponentName, user, true);
+ }
+
+ @Test
public void testUninstallReceiverExported() {
+ mTileServiceManager.startLifecycleManagerAndAddTile();
ArgumentCaptor<IntentFilter> intentFilterCaptor =
ArgumentCaptor.forClass(IntentFilter.class);
- Mockito.verify(mMockContext).registerReceiverAsUser(
- Mockito.any(),
- Mockito.any(),
+ verify(mMockContext).registerReceiverAsUser(
+ any(),
+ any(),
intentFilterCaptor.capture(),
- Mockito.any(),
- Mockito.any(),
- Mockito.eq(Context.RECEIVER_EXPORTED)
+ any(),
+ any(),
+ eq(Context.RECEIVER_EXPORTED)
);
IntentFilter filter = intentFilterCaptor.getValue();
assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED));
@@ -99,38 +143,41 @@
@Test
public void testSetBindRequested() {
+ mTileServiceManager.startLifecycleManagerAndAddTile();
// Request binding.
mTileServiceManager.setBindRequested(true);
mTileServiceManager.setLastUpdate(0);
mTileServiceManager.calculateBindPriority(5);
- Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+ verify(mTileServices, times(2)).recalculateBindAllowance();
assertEquals(5, mTileServiceManager.getBindPriority());
// Verify same state doesn't trigger recalculating for no reason.
mTileServiceManager.setBindRequested(true);
- Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+ verify(mTileServices, times(2)).recalculateBindAllowance();
mTileServiceManager.setBindRequested(false);
mTileServiceManager.calculateBindPriority(5);
- Mockito.verify(mTileServices, Mockito.times(3)).recalculateBindAllowance();
+ verify(mTileServices, times(3)).recalculateBindAllowance();
assertEquals(Integer.MIN_VALUE, mTileServiceManager.getBindPriority());
}
@Test
public void testPendingClickPriority() {
- Mockito.when(mTileLifecycle.hasPendingClick()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ when(mTileLifecycle.hasPendingClick()).thenReturn(true);
mTileServiceManager.calculateBindPriority(0);
assertEquals(Integer.MAX_VALUE, mTileServiceManager.getBindPriority());
}
@Test
public void testBind() {
+ mTileServiceManager.startLifecycleManagerAndAddTile();
// Trigger binding requested and allowed.
mTileServiceManager.setBindRequested(true);
mTileServiceManager.setBindAllowed(true);
ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
- Mockito.verify(mTileLifecycle, Mockito.times(1)).setBindService(captor.capture());
+ verify(mTileLifecycle, times(1)).setBindService(captor.capture());
assertTrue((boolean) captor.getValue());
mTileServiceManager.setBindRequested(false);
@@ -141,7 +188,7 @@
mTileServiceManager.setBindAllowed(false);
captor = ArgumentCaptor.forClass(Boolean.class);
- Mockito.verify(mTileLifecycle, Mockito.times(2)).setBindService(captor.capture());
+ verify(mTileLifecycle, times(2)).setBindService(captor.capture());
assertFalse((boolean) captor.getValue());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 471ddfd..213eca8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
@@ -118,6 +119,8 @@
private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock
private TileLifecycleManager mTileLifecycleManager;
+ @Mock
+ private UserFileManager mUserFileManager;
@Before
public void setUp() throws Exception {
@@ -149,7 +152,8 @@
mSecureSettings,
mock(CustomTileStatePersister.class),
mTileServiceRequestControllerBuilder,
- mTileLifecycleManagerFactory);
+ mTileLifecycleManagerFactory,
+ mUserFileManager);
mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,
mUserTracker, mKeyguardStateController, mCommandQueue);
}
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/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 87fca1f..7e07040 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -30,13 +30,13 @@
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -331,6 +331,47 @@
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
+ @Test
+ fun screenOff_whileFolded_hingeAngleProviderRemainsOff() {
+ setFoldState(folded = true)
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+
+ screenOnStatusProvider.notifyScreenTurningOff()
+
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+ }
+
+ @Test
+ fun screenOff_whileUnfolded_hingeAngleProviderStops() {
+ setFoldState(folded = false)
+ assertThat(testHingeAngleProvider.isStarted).isTrue()
+
+ screenOnStatusProvider.notifyScreenTurningOff()
+
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+ }
+
+ @Test
+ fun screenOn_whileUnfoldedAndScreenOff_hingeAngleProviderStarted() {
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOff()
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+
+ screenOnStatusProvider.notifyScreenTurningOn()
+
+ assertThat(testHingeAngleProvider.isStarted).isTrue()
+ }
+
+ @Test
+ fun screenOn_whileFolded_hingeAngleRemainsOff() {
+ setFoldState(folded = true)
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+
+ screenOnStatusProvider.notifyScreenTurningOn()
+
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+ }
+
private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
}
@@ -391,6 +432,14 @@
fun notifyScreenTurnedOn() {
callbacks.forEach { it.onScreenTurnedOn() }
}
+
+ fun notifyScreenTurningOn() {
+ callbacks.forEach { it.onScreenTurningOn() }
+ }
+
+ fun notifyScreenTurningOff() {
+ callbacks.forEach { it.onScreenTurningOff() }
+ }
}
private class TestHingeAngleProvider : HingeAngleProvider {
@@ -398,11 +447,11 @@
var isStarted: Boolean = false
override fun start() {
- isStarted = true;
+ isStarted = true
}
override fun stop() {
- isStarted = false;
+ isStarted = false
}
override fun addCallback(listener: Consumer<Float>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
new file mode 100644
index 0000000..d886ffd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.util
+
+import android.content.SharedPreferences
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FakeSharedPreferencesTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var listener: SharedPreferences.OnSharedPreferenceChangeListener
+
+ private lateinit var sharedPreferences: SharedPreferences
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ sharedPreferences = FakeSharedPreferences()
+ }
+
+ @Test
+ fun testGetString_default() {
+ val default = "default"
+ val result = sharedPreferences.getString("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetStringSet_default() {
+ val default = setOf("one", "two")
+ val result = sharedPreferences.getStringSet("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetInt_default() {
+ val default = 10
+ val result = sharedPreferences.getInt("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetLong_default() {
+ val default = 11L
+ val result = sharedPreferences.getLong("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetFloat_default() {
+ val default = 1.3f
+ val result = sharedPreferences.getFloat("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetBoolean_default() {
+ val default = true
+ val result = sharedPreferences.getBoolean("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testPutValuesAndRetrieve() {
+ val editor = sharedPreferences.edit()
+ val data = listOf<Data<*>>(
+ Data(
+ "keyString",
+ "value",
+ SharedPreferences.Editor::putString,
+ { getString(it, "") }
+ ),
+ Data(
+ "keyStringSet",
+ setOf("one", "two"),
+ SharedPreferences.Editor::putStringSet,
+ { getStringSet(it, emptySet()) }
+ ),
+ Data("keyInt", 10, SharedPreferences.Editor::putInt, { getInt(it, 0) }),
+ Data("keyLong", 11L, SharedPreferences.Editor::putLong, { getLong(it, 0L) }),
+ Data(
+ "keyFloat",
+ 1.3f,
+ SharedPreferences.Editor::putFloat,
+ { getFloat(it, 0f) }
+ ),
+ Data(
+ "keyBoolean",
+ true,
+ SharedPreferences.Editor::putBoolean,
+ { getBoolean(it, false) }
+ )
+ )
+
+ data.fold(editor) { ed, d ->
+ d.set(ed)
+ }
+ editor.commit()
+
+ data.forEach {
+ assertThat(it.get(sharedPreferences)).isEqualTo(it.value)
+ }
+ }
+
+ @Test
+ fun testContains() {
+ sharedPreferences.edit().putInt("key", 10).commit()
+
+ assertThat(sharedPreferences.contains("key")).isTrue()
+ assertThat(sharedPreferences.contains("other")).isFalse()
+ }
+
+ @Test
+ fun testOverwrite() {
+ sharedPreferences.edit().putInt("key", 10).commit()
+ sharedPreferences.edit().putInt("key", 11).commit()
+
+ assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11)
+ }
+
+ @Test
+ fun testDeleteString() {
+ sharedPreferences.edit().putString("key", "value").commit()
+ sharedPreferences.edit().putString("key", null).commit()
+
+ assertThat(sharedPreferences.contains("key")).isFalse()
+ }
+
+ @Test
+ fun testDeleteAndReplaceString() {
+ sharedPreferences.edit().putString("key", "value").commit()
+ sharedPreferences.edit().putString("key", "other").putString("key", null).commit()
+
+ assertThat(sharedPreferences.getString("key", "")).isEqualTo("other")
+ }
+
+ @Test
+ fun testDeleteStringSet() {
+ sharedPreferences.edit().putStringSet("key", setOf("one")).commit()
+ sharedPreferences.edit().putStringSet("key", setOf("two")).commit()
+
+ assertThat(sharedPreferences.getStringSet("key", emptySet())).isEqualTo(setOf("two"))
+ }
+
+ @Test
+ fun testClear() {
+ sharedPreferences.edit().putInt("keyInt", 1).putString("keyString", "a").commit()
+ sharedPreferences.edit().clear().commit()
+
+ assertThat(sharedPreferences.contains("keyInt")).isFalse()
+ assertThat(sharedPreferences.contains("keyString")).isFalse()
+ }
+
+ @Test
+ fun testClearAndWrite() {
+ sharedPreferences.edit().putInt("key", 10).commit()
+ sharedPreferences.edit().putInt("key", 11).clear().commit()
+
+ assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11)
+ }
+
+ @Test
+ fun testListenerNotifiedOnChanges() {
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+
+ sharedPreferences.edit().putInt("keyInt", 10).putString("keyString", "value").commit()
+
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyInt")
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString")
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun testListenerNotifiedOnClear() {
+ sharedPreferences.edit().putInt("keyInt", 10).commit()
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+
+ sharedPreferences.edit().clear().commit()
+
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, null)
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun testListenerNotifiedOnRemoval() {
+ sharedPreferences.edit()
+ .putString("keyString", "a")
+ .putStringSet("keySet", setOf("a"))
+ .commit()
+
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+ sharedPreferences.edit().putString("keyString", null).putStringSet("keySet", null).commit()
+
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString")
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keySet")
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun testListenerUnregistered() {
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
+ sharedPreferences.edit().putInt("key", 10).commit()
+
+ verify(listener, never()).onSharedPreferenceChanged(eq(sharedPreferences), anyString())
+ }
+
+ @Test
+ fun testSharedPreferencesOnlyModifiedOnCommit() {
+ sharedPreferences.edit().putInt("key", 10)
+
+ assertThat(sharedPreferences.contains("key")).isFalse()
+ }
+
+ private data class Data<T>(
+ val key: String,
+ val value: T,
+ private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor,
+ private val getter: SharedPreferences.(String) -> T
+ ) {
+ fun set(editor: SharedPreferences.Editor): SharedPreferences.Editor {
+ return editor.setter(key, value)
+ }
+
+ fun get(sharedPreferences: SharedPreferences): T {
+ return sharedPreferences.getter(key)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
new file mode 100644
index 0000000..4a881a7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.util
+
+import android.content.SharedPreferences
+
+/**
+ * Fake [SharedPreferences] to use within tests
+ *
+ * This will act in the same way as a real one for a particular file, but will store all the
+ * data in memory in the instance.
+ *
+ * [SharedPreferences.Editor.apply] and [SharedPreferences.Editor.commit] both act in the same way,
+ * synchronously modifying the stored data. Listeners are dispatched in the same thread, also
+ * synchronously.
+ */
+class FakeSharedPreferences : SharedPreferences {
+ private val data = mutableMapOf<String, Any>()
+ private val listeners = mutableSetOf<SharedPreferences.OnSharedPreferenceChangeListener>()
+
+ override fun getAll(): Map<String, *> {
+ return data
+ }
+
+ override fun getString(key: String, defValue: String?): String? {
+ return data.getOrDefault(key, defValue) as? String?
+ }
+
+ override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
+ return data.getOrDefault(key, defValues) as? MutableSet<String>?
+ }
+
+ override fun getInt(key: String, defValue: Int): Int {
+ return data.getOrDefault(key, defValue) as Int
+ }
+
+ override fun getLong(key: String, defValue: Long): Long {
+ return data.getOrDefault(key, defValue) as Long
+ }
+
+ override fun getFloat(key: String, defValue: Float): Float {
+ return data.getOrDefault(key, defValue) as Float
+ }
+
+ override fun getBoolean(key: String, defValue: Boolean): Boolean {
+ return data.getOrDefault(key, defValue) as Boolean
+ }
+
+ override fun contains(key: String): Boolean {
+ return key in data
+ }
+
+ override fun edit(): SharedPreferences.Editor {
+ return Editor()
+ }
+
+ override fun registerOnSharedPreferenceChangeListener(
+ listener: SharedPreferences.OnSharedPreferenceChangeListener
+ ) {
+ listeners.add(listener)
+ }
+
+ override fun unregisterOnSharedPreferenceChangeListener(
+ listener: SharedPreferences.OnSharedPreferenceChangeListener
+ ) {
+ listeners.remove(listener)
+ }
+
+ private inner class Editor : SharedPreferences.Editor {
+
+ private var clear = false
+ private val changes = mutableMapOf<String, Any>()
+ private val removals = mutableSetOf<String>()
+
+ override fun putString(key: String, value: String?): SharedPreferences.Editor {
+ if (value != null) {
+ changes[key] = value
+ } else {
+ removals.add(key)
+ }
+ return this
+ }
+
+ override fun putStringSet(
+ key: String,
+ values: MutableSet<String>?
+ ): SharedPreferences.Editor {
+ if (values != null) {
+ changes[key] = values
+ } else {
+ removals.add(key)
+ }
+ return this
+ }
+
+ override fun putInt(key: String, value: Int): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun putLong(key: String, value: Long): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun remove(key: String): SharedPreferences.Editor {
+ removals.add(key)
+ return this
+ }
+
+ override fun clear(): SharedPreferences.Editor {
+ clear = true
+ return this
+ }
+
+ override fun commit(): Boolean {
+ if (clear) {
+ data.clear()
+ }
+ removals.forEach { data.remove(it) }
+ data.putAll(changes)
+ val keys = removals + data.keys
+ if (clear || removals.isNotEmpty() || data.isNotEmpty()) {
+ listeners.forEach { listener ->
+ if (clear) {
+ listener.onSharedPreferenceChanged(this@FakeSharedPreferences, null)
+ }
+ keys.forEach {
+ listener.onSharedPreferenceChanged(this@FakeSharedPreferences, it)
+ }
+ }
+ }
+ return true
+ }
+
+ override fun apply() {
+ commit()
+ }
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index e8038fd..19cfc80 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -65,6 +65,7 @@
private val halfOpenedTimeoutMillis: Int = config.halfFoldedTimeoutMillis
private var isFolded = false
+ private var isScreenOn = false
private var isUnfoldHandled = true
override fun start() {
@@ -198,6 +199,25 @@
isUnfoldHandled = true
}
}
+
+ override fun onScreenTurningOn() {
+ isScreenOn = true
+ updateHingeAngleProviderState()
+ }
+
+ override fun onScreenTurningOff() {
+ isScreenOn = false
+ updateHingeAngleProviderState()
+ }
+ }
+
+ /** While the screen is off or the device is folded, hinge angle updates are not needed. */
+ private fun updateHingeAngleProviderState() {
+ if (isScreenOn && !isFolded) {
+ hingeAngleProvider.start()
+ } else {
+ hingeAngleProvider.stop()
+ }
}
private inner class HingeAngleListener : Consumer<Float> {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index 3fc5d61..577137c 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -30,8 +30,10 @@
private val sensorListener = HingeAngleSensorListener()
private val listeners: MutableList<Consumer<Float>> = arrayListOf()
+ var started = false
override fun start() = executor.execute {
+ if (started) return@execute
Trace.beginSection("HingeSensorAngleProvider#start")
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
sensorManager.registerListener(
@@ -40,10 +42,13 @@
SensorManager.SENSOR_DELAY_FASTEST
)
Trace.endSection()
+ started = true
}
override fun stop() = executor.execute {
+ if (!started) return@execute
sensorManager.unregisterListener(sensorListener)
+ started = false
}
override fun removeCallback(listener: Consumer<Float>) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index d95e050..f09b53d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -25,5 +25,15 @@
* Called when the screen is on and ready (windows are drawn and screen blocker is removed)
*/
fun onScreenTurnedOn()
+
+ /**
+ * Called when the screen is starting to be turned off.
+ */
+ fun onScreenTurningOff()
+
+ /**
+ * Called when the screen is starting to be turned on.
+ */
+ fun onScreenTurningOn()
}
}
diff --git a/services/Android.bp b/services/Android.bp
index 70692a6..1e4ce19 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -102,6 +102,7 @@
":services.profcollect-sources",
":services.restrictions-sources",
":services.searchui-sources",
+ ":services.selectiontoolbar-sources",
":services.smartspace-sources",
":services.speech-sources",
":services.systemcaptions-sources",
@@ -157,6 +158,7 @@
"services.profcollect",
"services.restrictions",
"services.searchui",
+ "services.selectiontoolbar",
"services.smartspace",
"services.speech",
"services.systemcaptions",
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index b5aa7b1..4f3fd64 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -33,6 +33,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.config.AutoBrightness;
import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
@@ -65,13 +66,13 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
import javax.xml.datatype.DatatypeConfigurationException;
/**
- * Reads and stores display-specific configurations.
- * File format:
- * <pre>
+ * Reads and stores display-specific configurations. File format:
+ * <pre>
* {@code
* <displayConfiguration>
* <densityMapping>
@@ -147,6 +148,15 @@
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
*
+ * <autoBrightness>
+ * <brighteningLightDebounceMillis>
+ * 2000
+ * </brighteningLightDebounceMillis>
+ * <darkeningLightDebounceMillis>
+ * 1000
+ * </darkeningLightDebounceMillis>
+ * </autoBrightness>
+ *
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
* <screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>
* <screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>
@@ -224,6 +234,9 @@
// Length of the ambient light horizon used to calculate short-term estimate of ambient light.
private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000;
+ // Invalid value of AutoBrightness brightening and darkening light debounce
+ private static final int INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE = -1;
+
@VisibleForTesting
static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
@@ -281,6 +294,14 @@
private String mLoadedFrom = null;
private Spline mSdrToHdrRatioSpline;
+ // Represents the auto-brightness brightening light debounce.
+ private long mAutoBrightnessBrighteningLightDebounce =
+ INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
+
+ // Represents the auto-brightness darkening light debounce.
+ private long mAutoBrightnessDarkeningLightDebounce =
+ INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
+
// Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
// data, which comes from the ddc, and the current one, which may be the DeviceConfig
// overwritten value.
@@ -293,8 +314,8 @@
}
/**
- * Creates an instance for the specified display.
- * Tries to find a file with identifier in the following priority order:
+ * Creates an instance for the specified display. Tries to find a file with identifier in the
+ * following priority order:
* <ol>
* <li>physicalDisplayId</li>
* <li>physicalDisplayId without a stable flag (old system)</li>
@@ -314,11 +335,12 @@
}
/**
- * Creates an instance using global values since no display device config xml exists.
- * Uses values from config or PowerManager.
+ * Creates an instance using global values since no display device config xml exists. Uses
+ * values from config or PowerManager.
*
- * @param context
- * @param useConfigXml
+ * @param context The context from which the DisplayDeviceConfig is to be constructed.
+ * @param useConfigXml A flag indicating if values are to be loaded from the configuration file,
+ * or the default values.
* @return A configuration instance.
*/
public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
@@ -450,8 +472,8 @@
}
/**
- * Calculates the backlight value, as recognised by the HAL, from the brightness value
- * given that the rest of the system deals with.
+ * Calculates the backlight value, as recognised by the HAL, from the brightness value given
+ * that the rest of the system deals with.
*
* @param brightness value on the framework scale of 0-1
* @return backlight value on the HAL scale of 0-1
@@ -502,13 +524,13 @@
if (DEBUG) {
Slog.d(TAG, "getHdrBrightnessFromSdr: sdr brightness " + brightness
- + " backlight " + backlight
- + " nits " + nits
- + " ratio " + ratio
- + " hdrNits " + hdrNits
- + " hdrBacklight " + hdrBacklight
- + " hdrBrightness " + hdrBrightness
- );
+ + " backlight " + backlight
+ + " nits " + nits
+ + " ratio " + ratio
+ + " hdrNits " + hdrNits
+ + " hdrBacklight " + hdrBacklight
+ + " hdrBrightness " + hdrBrightness
+ );
}
return hdrBrightness;
}
@@ -590,8 +612,8 @@
/**
* @param quirkValue The quirk to test.
- * @return {@code true} if the specified quirk is present in this configuration,
- * {@code false} otherwise.
+ * @return {@code true} if the specified quirk is present in this configuration, {@code false}
+ * otherwise.
*/
public boolean hasQuirk(String quirkValue) {
return mQuirks != null && mQuirks.contains(quirkValue);
@@ -625,6 +647,20 @@
return BrightnessThrottlingData.create(mBrightnessThrottlingData);
}
+ /**
+ * @return Auto brightness darkening light debounce
+ */
+ public long getAutoBrightnessDarkeningLightDebounce() {
+ return mAutoBrightnessDarkeningLightDebounce;
+ }
+
+ /**
+ * @return Auto brightness brightening light debounce
+ */
+ public long getAutoBrightnessBrighteningLightDebounce() {
+ return mAutoBrightnessBrighteningLightDebounce;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -663,14 +699,18 @@
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
+ ", mDensityMapping= " + mDensityMapping
+ + ", mAutoBrightnessBrighteningLightDebounce= "
+ + mAutoBrightnessBrighteningLightDebounce
+ + ", mAutoBrightnessDarkeningLightDebounce= "
+ + mAutoBrightnessDarkeningLightDebounce
+ "}";
}
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);
@@ -719,6 +759,7 @@
loadProxSensorFromDdc(config);
loadAmbientHorizonFromDdc(config);
loadBrightnessChangeThresholds(config);
+ loadAutoBrightnessConfigValues(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -899,8 +940,8 @@
if (i > 0) {
if (nits[i] < nits[i - 1]) {
Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest "
- + " of configuration. nits: " + nits[i] + " < "
- + nits[i - 1]);
+ + " of configuration. nits: " + nits[i] + " < "
+ + nits[i - 1]);
return null;
}
}
@@ -927,7 +968,7 @@
final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
// At least 1 point is guaranteed by the display device config schema
List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
- new ArrayList<>(points.size());
+ new ArrayList<>(points.size());
boolean badConfig = false;
for (BrightnessThrottlingPoint point : points) {
@@ -938,7 +979,7 @@
}
throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
- convertThermalStatus(status), point.getBrightness().floatValue()));
+ convertThermalStatus(status), point.getBrightness().floatValue()));
}
if (!badConfig) {
@@ -947,6 +988,41 @@
}
}
+ private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
+ loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
+ loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
+ }
+
+ /**
+ * Loads the auto-brightness brightening light debounce. Internally, this takes care of loading
+ * the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadAutoBrightnessBrighteningLightDebounce(AutoBrightness autoBrightnessConfig) {
+ if (autoBrightnessConfig == null
+ || autoBrightnessConfig.getBrighteningLightDebounceMillis() == null) {
+ mAutoBrightnessBrighteningLightDebounce = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
+ } else {
+ mAutoBrightnessBrighteningLightDebounce =
+ autoBrightnessConfig.getBrighteningLightDebounceMillis().intValue();
+ }
+ }
+
+ /**
+ * Loads the auto-brightness darkening light debounce. Internally, this takes care of loading
+ * the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadAutoBrightnessDarkeningLightDebounce(AutoBrightness autoBrightnessConfig) {
+ if (autoBrightnessConfig == null
+ || autoBrightnessConfig.getDarkeningLightDebounceMillis() == null) {
+ mAutoBrightnessDarkeningLightDebounce = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce);
+ } else {
+ mAutoBrightnessDarkeningLightDebounce =
+ autoBrightnessConfig.getDarkeningLightDebounceMillis().intValue();
+ }
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -1058,17 +1134,17 @@
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]);
}
mBrightnessToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mBrightness, mBacklight)
- : Spline.createSpline(mBrightness, mBacklight);
+ ? Spline.createLinearSpline(mBrightness, mBacklight)
+ : Spline.createSpline(mBrightness, mBacklight);
mBacklightToBrightnessSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mBacklight, mBrightness)
- : Spline.createSpline(mBacklight, mBrightness);
+ ? Spline.createLinearSpline(mBacklight, mBrightness)
+ : Spline.createSpline(mBacklight, mBrightness);
mBacklightToNitsSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mBacklight, mNits)
- : Spline.createSpline(mBacklight, mNits);
+ ? Spline.createLinearSpline(mBacklight, mNits)
+ : Spline.createSpline(mBacklight, mNits);
mNitsToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mNits, mBacklight)
- : Spline.createSpline(mNits, mBacklight);
+ ? Spline.createLinearSpline(mNits, mBacklight)
+ : Spline.createSpline(mNits, mBacklight);
}
private void loadQuirks(DisplayConfiguration config) {
@@ -1111,7 +1187,7 @@
if (mHbmData.minimumHdrPercentOfScreen > 1
|| mHbmData.minimumHdrPercentOfScreen < 0) {
Slog.w(TAG, "Invalid minimum HDR percent of screen: "
- + String.valueOf(mHbmData.minimumHdrPercentOfScreen));
+ + String.valueOf(mHbmData.minimumHdrPercentOfScreen));
mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
}
} else {
@@ -1235,7 +1311,7 @@
ambientBrightnessThresholds.getDarkeningThresholds();
final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
- final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+ final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
if (ambientBrighteningThreshold != null) {
mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
@@ -1330,8 +1406,8 @@
}
/**
- * @return True if the sensor matches both the specified name and type, or one if only
- * one is specified (not-empty). Always returns false if both parameters are null or empty.
+ * @return True if the sensor matches both the specified name and type, or one if only one
+ * is specified (not-empty). Always returns false if both parameters are null or empty.
*/
public boolean matches(String sensorName, String sensorType) {
final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
@@ -1446,6 +1522,7 @@
return otherThrottlingLevel.thermalStatus == this.thermalStatus
&& otherThrottlingLevel.brightness == this.brightness;
}
+
@Override
public int hashCode() {
int result = 1;
@@ -1455,8 +1532,11 @@
}
}
- static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels)
- {
+
+ /**
+ * Creates multiple teperature based throttling levels of brightness
+ */
+ public static BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) {
if (throttlingLevels == null || throttlingLevels.size() == 0) {
Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");
return null;
@@ -1498,8 +1578,9 @@
}
static public BrightnessThrottlingData create(BrightnessThrottlingData other) {
- if (other == null)
+ if (other == null) {
return null;
+ }
return BrightnessThrottlingData.create(other.throttlingLevels);
}
@@ -1508,8 +1589,8 @@
@Override
public String toString() {
return "BrightnessThrottlingData{"
- + "throttlingLevels:" + throttlingLevels
- + "} ";
+ + "throttlingLevels:" + throttlingLevels
+ + "} ";
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 8781a8d..9c86076 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -985,10 +985,10 @@
screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
screenDarkeningMinThreshold, screenBrighteningMinThreshold);
- long brighteningLightDebounce = resources.getInteger(
- com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
- long darkeningLightDebounce = resources.getInteger(
- com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce);
+ long brighteningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessBrighteningLightDebounce();
+ long darkeningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessDarkeningLightDebounce();
boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
@@ -1204,6 +1204,7 @@
}
assert(state != Display.STATE_UNKNOWN);
+ boolean skipRampBecauseOfProximityChangeToNegative = false;
// Apply the proximity sensor.
if (mProximitySensor != null) {
if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
@@ -1241,6 +1242,7 @@
// the screen back on. Also turn it back on if we've been asked to ignore the
// prox sensor temporarily.
mScreenOffBecauseOfProximity = false;
+ skipRampBecauseOfProximityChangeToNegative = true;
sendOnProximityNegativeWithWakelock();
}
} else {
@@ -1313,9 +1315,6 @@
}
final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
- if (autoBrightnessAdjustmentChanged) {
- mTemporaryAutoBrightnessAdjustment = Float.NaN;
- }
// Use the autobrightness adjustment override if set.
final float autoBrightnessAdjustment;
@@ -1523,8 +1522,8 @@
final boolean wasOrWillBeInVr =
(state == Display.STATE_VR || oldState == Display.STATE_VR);
- final boolean initialRampSkip =
- state == Display.STATE_ON && mSkipRampState != RAMP_STATE_SKIP_NONE;
+ final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
+ != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
// While dozing, sometimes the brightness is split into buckets. Rather than animating
// through the buckets, which is unlikely to be smooth in the first place, just jump
// right to the suggested brightness.
@@ -2291,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();
@@ -2367,6 +2367,7 @@
}
mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
mPendingAutoBrightnessAdjustment = Float.NaN;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
return true;
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 25d187f..0769406 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1725,16 +1725,6 @@
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
- // Check if current activity is letterboxed in order create a LetterboxDetails
- // component to be passed to SysUI for status bar treatment
- final ActivityRecord currentActivity = win.getActivityRecord();
- if (currentActivity != null) {
- final LetterboxDetails currentLetterboxDetails = currentActivity
- .mLetterboxUiController.getLetterboxDetails();
- if (currentLetterboxDetails != null) {
- mLetterboxDetails.add(currentLetterboxDetails);
- }
- }
}
}
@@ -1752,6 +1742,17 @@
mNavBarBackgroundWindow = win;
}
}
+
+ // Check if current activity is letterboxed in order create a LetterboxDetails
+ // component to be passed to SysUI for status bar treatment
+ final ActivityRecord currentActivity = win.getActivityRecord();
+ if (currentActivity != null) {
+ final LetterboxDetails currentLetterboxDetails = currentActivity
+ .mLetterboxUiController.getLetterboxDetails();
+ if (currentLetterboxDetails != null) {
+ mLetterboxDetails.add(currentLetterboxDetails);
+ }
+ }
} else if (win.isDimming()) {
if (mStatusBar != null) {
addStatusBarAppearanceRegionsForDimmingWindow(
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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 09044e7..073b131c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -45,6 +45,8 @@
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
+ <xs:element type="autoBrightness" name="autoBrightness" minOccurs="0"
+ maxOccurs="1" />
<xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">
<xs:annotation name="final"/>
</xs:element>
@@ -101,6 +103,21 @@
<!-- Type definitions -->
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="displayQuirks">
<xs:sequence>
<xs:element name="quirk" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
@@ -341,5 +358,4 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
-
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e8b13ca..e9a9269 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -1,6 +1,14 @@
// Signature format: 2.0
package com.android.server.display.config {
+ public class AutoBrightness {
+ ctor public AutoBrightness();
+ method public final java.math.BigInteger getBrighteningLightDebounceMillis();
+ method public final java.math.BigInteger getDarkeningLightDebounceMillis();
+ method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
+ method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method @NonNull public final java.math.BigDecimal getMinimum();
@@ -40,6 +48,7 @@
method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
+ method public com.android.server.display.config.AutoBrightness getAutoBrightness();
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
@@ -58,6 +67,7 @@
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
+ method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 66c9f55..8fd4b5a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -338,6 +338,8 @@
"com.android.server.contentcapture.ContentCaptureManagerService";
private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
"com.android.server.translation.TranslationManagerService";
+ private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS =
+ "com.android.server.selectiontoolbar.SelectionToolbarManagerService";
private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
"com.android.server.musicrecognition.MusicRecognitionManagerService";
private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
@@ -2634,6 +2636,11 @@
Slog.d(TAG, "TranslationService not defined by OEM");
}
+ // Selection toolbar service
+ t.traceBegin("StartSelectionToolbarManagerService");
+ mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
// NOTE: ClipboardService depends on ContentCapture and Autofill
t.traceBegin("StartClipboardService");
mSystemServiceManager.startService(ClipboardService.class);
diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp
new file mode 100644
index 0000000..cc6405f
--- /dev/null
+++ b/services/selectiontoolbar/Android.bp
@@ -0,0 +1,22 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "services.selectiontoolbar-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.selectiontoolbar",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.selectiontoolbar-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 7939303..03ea613 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -86,6 +86,8 @@
0.0f);
assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+ assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
+ assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
@@ -109,6 +111,10 @@
+ "<nits>800.0</nits>\n"
+ "</point>\n"
+ "</screenBrightnessMap>\n"
+ + "<autoBrightness>\n"
+ + "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+ + "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+ + "</autoBrightness>\n"
+ "<highBrightnessMode enabled=\"true\">\n"
+ "<transitionPoint>0.62</transitionPoint>\n"
+ "<minimumLux>10000</minimumLux>\n"
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 21839aa..fde6e3c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -102,7 +102,6 @@
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
-import org.mockito.Mockito;
/**
* Tests for Size Compatibility mode.
@@ -2165,6 +2164,40 @@
}
@Test
+ public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() {
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
+ .setNotch(100)
+ .build();
+ setUpApp(display);
+ TestWindowState statusBar = addStatusBar(mActivity.mDisplayContent);
+ spyOn(statusBar);
+ doReturn(new Rect(0, 0, statusBar.mRequestedWidth, statusBar.mRequestedHeight))
+ .when(statusBar).getFrame();
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ // Refresh the letterbox
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(0, 750, 1000, 1950));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
+ mBounds,
+ mActivity.getDisplayContent().getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
+ @Test
public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(2800, 1000);
@@ -2785,7 +2818,7 @@
return w;
}
- private static void addStatusBar(DisplayContent displayContent) {
+ private static TestWindowState addStatusBar(DisplayContent displayContent) {
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
doReturn(true).when(displayPolicy).hasStatusBar();
displayPolicy.onConfigurationChanged();
@@ -2806,6 +2839,7 @@
displayPolicy.addWindowLw(statusBar, attrs);
displayPolicy.layoutWindowLw(statusBar, null, displayContent.mDisplayFrames);
+ return statusBar;
}
/**
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();