diff options
145 files changed, 2983 insertions, 1309 deletions
diff --git a/apct-tests/perftests/core/src/android/os/OWNERS b/apct-tests/perftests/core/src/android/os/OWNERS index a1719c9c31d1..76ab30334634 100644 --- a/apct-tests/perftests/core/src/android/os/OWNERS +++ b/apct-tests/perftests/core/src/android/os/OWNERS @@ -1 +1,4 @@ -per-file PackageParsingPerfTest.kt = file:/services/core/java/com/android/server/pm/OWNERS
\ No newline at end of file +per-file PackageParsingPerfTest.kt = file:/services/core/java/com/android/server/pm/OWNERS + +# Bug component: 345036 +per-file VibratorPerfTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
\ No newline at end of file diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 853528f908bb..12ffdb37f106 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -65,6 +65,7 @@ import android.app.servertransaction.PendingTransactionActions.StopInfo; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.TransactionExecutor; import android.app.servertransaction.TransactionExecutorHelper; +import android.app.servertransaction.WindowTokenClientController; import android.bluetooth.BluetoothFrameworkInitializer; import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; @@ -6221,6 +6222,18 @@ public final class ActivityThread extends ClientTransactionHandler false /* clearPending */); } + @Override + public void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken, + @NonNull Configuration configuration, int displayId) { + WindowTokenClientController.getInstance().onWindowContextConfigurationChanged(clientToken, + configuration, displayId); + } + + @Override + public void handleWindowContextWindowRemoval(@NonNull IBinder clientToken) { + WindowTokenClientController.getInstance().onWindowContextWindowRemoved(clientToken); + } + /** * Sends windowing mode change callbacks to {@link Activity} if applicable. * diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 49fb794a0a25..f7a43f42f2ef 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -163,6 +163,13 @@ public abstract class ClientTransactionHandler { public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r, Configuration overrideConfig, int displayId); + /** Deliver {@link android.window.WindowContext} configuration change. */ + public abstract void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken, + @NonNull Configuration configuration, int displayId); + + /** Deliver {@link android.window.WindowContext} window removal event. */ + public abstract void handleWindowContextWindowRemoval(@NonNull IBinder clientToken); + /** Deliver result from another activity. */ public abstract void handleSendResult( @NonNull ActivityClientRecord r, List<ResultInfo> results, String reason); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 59b0dacd7cd7..1f95497a7dba 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UiContext; +import android.app.servertransaction.WindowTokenClientController; import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; @@ -3276,7 +3277,8 @@ class ContextImpl extends Context { // if this Context is not a WindowContext. WindowContext finalization is handled in // WindowContext class. if (mToken instanceof WindowTokenClient && mOwnsToken) { - ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); + WindowTokenClientController.getInstance().detachIfNeeded( + (WindowTokenClient) mToken); } super.finalize(); } @@ -3304,7 +3306,7 @@ class ContextImpl extends Context { final WindowTokenClient token = new WindowTokenClient(); final ContextImpl context = systemContext.createWindowContextBase(token, displayId); token.attachContext(context); - token.attachToDisplayContent(displayId); + WindowTokenClientController.getInstance().attachToDisplayContent(token, displayId); context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI; context.mOwnsToken = true; diff --git a/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java new file mode 100644 index 000000000000..3ac642fd4664 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2023 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.app.servertransaction; + +import static android.view.Display.INVALID_DISPLAY; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.Parcel; + +import java.util.Objects; + +/** + * {@link android.window.WindowContext} configuration change message. + * @hide + */ +public class WindowContextConfigurationChangeItem extends ClientTransactionItem { + + @Nullable + private IBinder mClientToken; + @Nullable + private Configuration mConfiguration; + private int mDisplayId; + + @Override + public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + @NonNull PendingTransactionActions pendingActions) { + client.handleWindowContextConfigurationChanged(mClientToken, mConfiguration, mDisplayId); + } + + // ObjectPoolItem implementation + + private WindowContextConfigurationChangeItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowContextConfigurationChangeItem obtain( + @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) { + WindowContextConfigurationChangeItem instance = + ObjectPool.obtain(WindowContextConfigurationChangeItem.class); + if (instance == null) { + instance = new WindowContextConfigurationChangeItem(); + } + instance.mClientToken = requireNonNull(clientToken); + instance.mConfiguration = requireNonNull(config); + instance.mDisplayId = displayId; + + return instance; + } + + @Override + public void recycle() { + mClientToken = null; + mConfiguration = null; + mDisplayId = INVALID_DISPLAY; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mClientToken); + dest.writeTypedObject(mConfiguration, flags); + dest.writeInt(mDisplayId); + } + + /** Reads from Parcel. */ + private WindowContextConfigurationChangeItem(@NonNull Parcel in) { + mClientToken = in.readStrongBinder(); + mConfiguration = in.readTypedObject(Configuration.CREATOR); + mDisplayId = in.readInt(); + } + + public static final @NonNull Creator<WindowContextConfigurationChangeItem> CREATOR = + new Creator<>() { + public WindowContextConfigurationChangeItem createFromParcel(Parcel in) { + return new WindowContextConfigurationChangeItem(in); + } + + public WindowContextConfigurationChangeItem[] newArray(int size) { + return new WindowContextConfigurationChangeItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowContextConfigurationChangeItem other = (WindowContextConfigurationChangeItem) o; + return Objects.equals(mClientToken, other.mClientToken) + && Objects.equals(mConfiguration, other.mConfiguration) + && mDisplayId == other.mDisplayId; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mClientToken); + result = 31 * result + Objects.hashCode(mConfiguration); + result = 31 * result + mDisplayId; + return result; + } + + @Override + public String toString() { + return "WindowContextConfigurationChangeItem{clientToken=" + mClientToken + + ", config=" + mConfiguration + + ", displayId=" + mDisplayId + + "}"; + } +} diff --git a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java new file mode 100644 index 000000000000..ed52a6496e95 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 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.app.servertransaction; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.os.Parcel; + +import java.util.Objects; + +/** + * {@link android.window.WindowContext} window removal message. + * @hide + */ +public class WindowContextWindowRemovalItem extends ClientTransactionItem { + + @Nullable + private IBinder mClientToken; + + @Override + public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + @NonNull PendingTransactionActions pendingActions) { + client.handleWindowContextWindowRemoval(mClientToken); + } + + // ObjectPoolItem implementation + + private WindowContextWindowRemovalItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowContextWindowRemovalItem obtain(@NonNull IBinder clientToken) { + WindowContextWindowRemovalItem instance = + ObjectPool.obtain(WindowContextWindowRemovalItem.class); + if (instance == null) { + instance = new WindowContextWindowRemovalItem(); + } + instance.mClientToken = requireNonNull(clientToken); + + return instance; + } + + @Override + public void recycle() { + mClientToken = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mClientToken); + } + + /** Reads from Parcel. */ + private WindowContextWindowRemovalItem(@NonNull Parcel in) { + mClientToken = in.readStrongBinder(); + } + + public static final @NonNull Creator<WindowContextWindowRemovalItem> CREATOR = new Creator<>() { + public WindowContextWindowRemovalItem createFromParcel(Parcel in) { + return new WindowContextWindowRemovalItem(in); + } + + public WindowContextWindowRemovalItem[] newArray(int size) { + return new WindowContextWindowRemovalItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowContextWindowRemovalItem other = (WindowContextWindowRemovalItem) o; + return Objects.equals(mClientToken, other.mClientToken); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mClientToken); + return result; + } + + @Override + public String toString() { + return "WindowContextWindowRemovalItem{clientToken=" + mClientToken + "}"; + } +} diff --git a/core/java/android/app/servertransaction/WindowTokenClientController.java b/core/java/android/app/servertransaction/WindowTokenClientController.java new file mode 100644 index 000000000000..5d123a043835 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowTokenClientController.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2023 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.app.servertransaction; + +import static android.view.WindowManager.LayoutParams.WindowType; +import static android.view.WindowManagerGlobal.getWindowManagerService; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.view.IWindowManager; +import android.window.WindowContext; +import android.window.WindowTokenClient; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch + * corresponding window configuration change from server side. + * @hide + */ +public class WindowTokenClientController { + + private static final String TAG = WindowTokenClientController.class.getSimpleName(); + private static WindowTokenClientController sController; + + private final Object mLock = new Object(); + + /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */ + @GuardedBy("mLock") + private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>(); + + /** Gets the singleton controller. */ + public static WindowTokenClientController getInstance() { + synchronized (WindowTokenClientController.class) { + if (sController == null) { + sController = new WindowTokenClientController(); + } + return sController; + } + } + + /** Overrides the {@link #getInstance()} for test only. */ + @VisibleForTesting + public static void overrideForTesting(@NonNull WindowTokenClientController controller) { + synchronized (WindowTokenClientController.class) { + sController = controller; + } + } + + private WindowTokenClientController() {} + + /** + * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. + * + * @param client The {@link WindowTokenClient} to attach. + * @param type The window type of the {@link WindowContext} + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @param options The window context launched option + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayArea(@NonNull WindowTokenClient client, + @WindowType int type, int displayId, @Nullable Bundle options) { + final Configuration configuration; + try { + configuration = getWindowManagerService() + .attachWindowContextToDisplayArea(client, type, displayId, options); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (configuration == null) { + return false; + } + onWindowContextTokenAttached(client, displayId, configuration); + return true; + } + + /** + * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}. + * + * @param client The {@link WindowTokenClient} to attach. + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) { + final IWindowManager wms = getWindowManagerService(); + // #createSystemUiContext may call this method before WindowManagerService is initialized. + if (wms == null) { + return false; + } + final Configuration configuration; + try { + configuration = wms.attachToDisplayContent(client, displayId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (configuration == null) { + return false; + } + onWindowContextTokenAttached(client, displayId, configuration); + return true; + } + + /** + * Attaches this {@link WindowTokenClient} to a {@code windowToken}. + * + * @param client The {@link WindowTokenClient} to attach. + * @param windowToken the window token to associated with + */ + public void attachToWindowToken(@NonNull WindowTokenClient client, + @NonNull IBinder windowToken) { + try { + getWindowManagerService().attachWindowContextToWindowToken(client, windowToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + // We don't report configuration change for now. + synchronized (mLock) { + mWindowTokenClientMap.put(client.asBinder(), client); + } + } + + /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */ + public void detachIfNeeded(@NonNull WindowTokenClient client) { + synchronized (mLock) { + if (mWindowTokenClientMap.remove(client.asBinder()) == null) { + return; + } + } + try { + getWindowManagerService().detachWindowContextFromWindowContainer(client); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, int displayId, + @NonNull Configuration configuration) { + synchronized (mLock) { + mWindowTokenClientMap.put(client.asBinder(), client); + } + client.onConfigurationChanged(configuration, displayId, + false /* shouldReportConfigChange */); + } + + /** Called when receives {@link WindowContextConfigurationChangeItem}. */ + public void onWindowContextConfigurationChanged(@NonNull IBinder clientToken, + @NonNull Configuration configuration, int displayId) { + final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); + if (windowTokenClient != null) { + windowTokenClient.onConfigurationChanged(configuration, displayId); + } + } + + /** Called when receives {@link WindowContextWindowRemovalItem}. */ + public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) { + final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); + if (windowTokenClient != null) { + windowTokenClient.onWindowTokenRemoved(); + } + } + + @Nullable + private WindowTokenClient getWindowTokenClient(@NonNull IBinder clientToken) { + final WindowTokenClient windowTokenClient; + synchronized (mLock) { + windowTokenClient = mWindowTokenClientMap.get(clientToken); + } + if (windowTokenClient == null) { + Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken); + } + return windowTokenClient; + } +} diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index ceaf337b2122..2584f047295d 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -119,7 +119,7 @@ public class Handler { * crashes (if a handler is sometimes created on a thread without a Looper active), or race * conditions, where the thread a handler is associated with is not what the author * anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper - * explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or + * explicitly, using {@link Looper#getMainLooper}, {@link android.view.View#getHandler}, or * similar. If the implicit thread local behavior is required for compatibility, use * {@code new Handler(Looper.myLooper())} to make it clear to readers. * @@ -144,7 +144,7 @@ public class Handler { * crashes (if a handler is sometimes created on a thread without a Looper active), or race * conditions, where the thread a handler is associated with is not what the author * anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper - * explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or + * explicitly, using {@link Looper#getMainLooper}, {@link android.view.View#getHandler}, or * similar. If the implicit thread local behavior is required for compatibility, use * {@code new Handler(Looper.myLooper(), callback)} to make it clear to readers. */ diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index f256210365ba..447c3bcdc186 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -27,6 +27,7 @@ import android.annotation.PluralsRes; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; +import android.graphics.Typeface; import android.icu.lang.UCharacter; import android.icu.text.CaseMap; import android.icu.text.Edits; @@ -2482,12 +2483,28 @@ public class TextUtils { if (ellipsizeDip == 0) { return gettingCleaned.toString(); } else { - // Truncate - final TextPaint paint = new TextPaint(); - paint.setTextSize(42); + final float assumedFontSizePx = 42; + if (Typeface.getSystemFontMap().isEmpty()) { + // In the system server, the font files may not be loaded, so unable to perform + // ellipsize, so use the estimated char count for the ellipsize. + + // The median of glyph widths of the Roboto is 0.57em, so use it as a reference + // of the glyph width. + final float assumedCharWidthInEm = 0.57f; + final float assumedCharWidthInPx = assumedFontSizePx * assumedCharWidthInEm; + + // Even if the argument name is `ellipsizeDip`, the unit of this argument is pixels. + final int charCount = (int) ((ellipsizeDip + 0.5f) / assumedCharWidthInPx); + return TextUtils.trimToSize(gettingCleaned.toString(), charCount) + + getEllipsisString(TruncateAt.END); + } else { + // Truncate + final TextPaint paint = new TextPaint(); + paint.setTextSize(assumedFontSizePx); - return TextUtils.ellipsize(gettingCleaned.toString(), paint, ellipsizeDip, - TextUtils.TruncateAt.END); + return TextUtils.ellipsize(gettingCleaned.toString(), paint, ellipsizeDip, + TextUtils.TruncateAt.END); + } } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 99a4f6b41ef3..6aa85062562c 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -34,6 +34,7 @@ import android.util.ArraySet; import android.util.Log; import android.view.inputmethod.InputMethodManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; @@ -184,6 +185,15 @@ public final class WindowManagerGlobal { } } + /** Overrides the {@link #getWindowManagerService()} for test only. */ + @VisibleForTesting + public static void overrideWindowManagerServiceForTesting( + @NonNull IWindowManager windowManager) { + synchronized (WindowManagerGlobal.class) { + sWindowManagerService = windowManager; + } + } + @UnsupportedAppUsage public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 8ba8b8cca5ed..0699bc1cb734 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -32,6 +32,7 @@ import android.os.SystemClock; import android.util.AttributeSet; import android.util.TimeUtils; import android.util.Xml; +import android.view.Choreographer; import android.view.InflateException; import org.xmlpull.v1.XmlPullParser; @@ -153,7 +154,13 @@ public class AnimationUtils { */ public static long getExpectedPresentationTimeNanos() { AnimationState state = sAnimationState.get(); - return state.mExpectedPresentationTimeNanos; + if (state.animationClockLocked) { + return state.mExpectedPresentationTimeNanos; + } + // When this methoed is called outside of a Choreographer callback, + // we obtain the value of expectedPresentTimeNanos from the Choreographer. + // This helps avoid returning a time that could potentially be earlier than current time. + return Choreographer.getInstance().getLatestExpectedPresentTimeNanos(); } /** diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index e9d7b9b25d91..a95748c6d425 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -43,7 +43,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewRootImpl; -import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; @@ -54,6 +53,7 @@ import java.lang.annotation.Retention; import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier; @@ -158,18 +158,13 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { boolean cancellable(); } - @GuardedBy("mLock") - @Nullable - private InputConnection mInputConnection; + @NonNull + private final AtomicReference<InputConnection> mInputConnectionRef; @NonNull private final Looper mLooper; private final Handler mH; - private final Object mLock = new Object(); - @GuardedBy("mLock") - private boolean mFinished = false; - private final InputMethodManager mParentInputMethodManager; private final WeakReference<View> mServedView; @@ -185,7 +180,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { RemoteInputConnectionImpl(@NonNull Looper looper, @NonNull InputConnection inputConnection, @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) { - mInputConnection = inputConnection; + mInputConnectionRef = new AtomicReference<>(inputConnection); mLooper = looper; mH = new Handler(mLooper); mParentInputMethodManager = inputMethodManager; @@ -197,9 +192,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { */ @Nullable public InputConnection getInputConnection() { - synchronized (mLock) { - return mInputConnection; - } + return mInputConnectionRef.get(); } /** @@ -215,9 +208,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { * {@link InputConnection#closeConnection()} as a result of {@link #deactivate()}. */ private boolean isFinished() { - synchronized (mLock) { - return mFinished; - } + return mInputConnectionRef.get() == null; } private boolean isActive() { @@ -386,10 +377,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { // TODO(b/199934664): See if we can remove this by providing a default impl. } } finally { - synchronized (mLock) { - mInputConnection = null; - mFinished = true; - } + mInputConnectionRef.set(null); Trace.traceEnd(Trace.TRACE_TAG_INPUT); } @@ -441,7 +429,6 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public String toString() { return "RemoteInputConnectionImpl{" + "connection=" + getInputConnection() - + " finished=" + isFinished() + " mParentInputMethodManager.isActive()=" + mParentInputMethodManager.isActive() + " mServedView=" + mServedView.get() + "}"; @@ -455,16 +442,14 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { * {@link DumpableInputConnection#dumpDebug(ProtoOutputStream, long)}. */ public void dumpDebug(ProtoOutputStream proto, long fieldId) { - synchronized (mLock) { - // Check that the call is initiated in the target thread of the current InputConnection - // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are - // executed on this thread. Otherwise the messages are dispatched to the correct thread - // in IInputConnectionWrapper, but this is not wanted while dumpng, for performance - // reasons. - if ((mInputConnection instanceof DumpableInputConnection) - && mLooper.isCurrentThread()) { - ((DumpableInputConnection) mInputConnection).dumpDebug(proto, fieldId); - } + final InputConnection ic = mInputConnectionRef.get(); + // Check that the call is initiated in the target thread of the current InputConnection + // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are + // executed on this thread. Otherwise the messages are dispatched to the correct thread + // in IInputConnectionWrapper, but this is not wanted while dumping, for performance + // reasons. + if ((ic instanceof DumpableInputConnection) && mLooper.isCurrentThread()) { + ((DumpableInputConnection) ic).dumpDebug(proto, fieldId); } } diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 4b9a957f541d..eb270e2fb2f0 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.servertransaction.WindowTokenClientController; import android.content.Context; import android.os.Bundle; import android.os.IBinder; @@ -104,7 +105,8 @@ public class WindowContextController { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options) + mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea( + mToken, type, displayId, options) ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED; if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) { Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:" @@ -140,13 +142,13 @@ public class WindowContextController { throw new IllegalStateException("The Window Context should have been attached" + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea); } - mToken.attachToWindowToken(windowToken); + WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken); } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) { - mToken.detachFromWindowContainerIfNeeded(); + WindowTokenClientController.getInstance().detachIfNeeded(mToken); mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED; if (DEBUG_ATTACH) { Log.d(TAG, "Detach Window Context."); diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index a208634abe78..47d3df870216 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -20,13 +20,12 @@ import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; import android.annotation.AnyThread; -import android.annotation.BinderThread; import android.annotation.MainThread; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityThread; import android.app.IWindowToken; import android.app.ResourcesManager; +import android.app.servertransaction.WindowTokenClientController; import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -36,14 +35,9 @@ import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; import android.util.Log; -import android.view.IWindowManager; -import android.view.WindowManager.LayoutParams.WindowType; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.pooled.PooledLambda; import java.lang.ref.WeakReference; @@ -70,15 +64,11 @@ public class WindowTokenClient extends IWindowToken.Stub { private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); - private IWindowManager mWms; - @GuardedBy("itself") private final Configuration mConfiguration = new Configuration(); private boolean mShouldDumpConfigForIme; - private boolean mAttachToWindowContainer; - private final Handler mHandler = ActivityThread.currentActivityThread().getHandler(); /** @@ -101,96 +91,15 @@ public class WindowTokenClient extends IWindowToken.Stub { } /** - * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. - * - * @param type The window type of the {@link WindowContext} - * @param displayId The {@link Context#getDisplayId() ID of display} to associate with - * @param options The window context launched option - * @return {@code true} if attaching successfully. - */ - public boolean attachToDisplayArea(@WindowType int type, int displayId, - @Nullable Bundle options) { - try { - final Configuration configuration = getWindowManagerService() - .attachWindowContextToDisplayArea(this, type, displayId, options); - if (configuration == null) { - return false; - } - onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); - mAttachToWindowContainer = true; - return true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}. - * - * @param displayId The {@link Context#getDisplayId() ID of display} to associate with - * @return {@code true} if attaching successfully. - */ - public boolean attachToDisplayContent(int displayId) { - final IWindowManager wms = getWindowManagerService(); - // #createSystemUiContext may call this method before WindowManagerService is initialized. - if (wms == null) { - return false; - } - try { - final Configuration configuration = wms.attachToDisplayContent(this, displayId); - if (configuration == null) { - return false; - } - onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); - mAttachToWindowContainer = true; - return true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Attaches this {@link WindowTokenClient} to a {@code windowToken}. - * - * @param windowToken the window token to associated with - */ - public void attachToWindowToken(IBinder windowToken) { - try { - getWindowManagerService().attachWindowContextToWindowToken(this, windowToken); - mAttachToWindowContainer = true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */ - public void detachFromWindowContainerIfNeeded() { - if (!mAttachToWindowContainer) { - return; - } - try { - getWindowManagerService().detachWindowContextFromWindowContainer(this); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - private IWindowManager getWindowManagerService() { - if (mWms == null) { - mWms = WindowManagerGlobal.getWindowManagerService(); - } - return mWms; - } - - /** * Called when {@link Configuration} updates from the server side receive. * * @param newConfig the updated {@link Configuration} * @param newDisplayId the updated {@link android.view.Display} ID */ - @BinderThread + @AnyThread @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { + // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig, newDisplayId, true /* shouldReportConfigChange */).recycleOnUse()); } @@ -207,15 +116,14 @@ public class WindowTokenClient extends IWindowToken.Stub { * {@code shouldReportConfigChange} is {@code true}, which is usually from * {@link IWindowToken#onConfigurationChanged(Configuration, int)} * directly, while this method could be run on any thread if it is used to initialize - * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)} - * or {@link #attachToDisplayContent(int)}. + * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea} + * or {@link WindowTokenClientController#attachToDisplayContent}. * * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration} * should be dispatched to listeners. * */ @AnyThread - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void onConfigurationChanged(Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { final Context context = mContextRef.get(); @@ -280,9 +188,10 @@ public class WindowTokenClient extends IWindowToken.Stub { } } - @BinderThread + @AnyThread @Override public void onWindowTokenRemoved() { + // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction mHandler.post(PooledLambda.obtainRunnable( WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse()); } diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index c2cfcd64c835..66b0158fbd67 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -1565,6 +1565,13 @@ public class LockPatternView extends View { mInStealthMode = ss.isInStealthMode(); } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + setSystemGestureExclusionRects(List.of(new Rect(left, top, right, bottom))); + } + /** * The parecelable for saving and restoring a lock pattern view. */ diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 48577416b3d0..c1b55cdfc6d7 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; @@ -29,6 +30,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.annotation.Nullable; import android.app.Activity; @@ -46,6 +49,7 @@ import android.app.servertransaction.ConfigurationChangeItem; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; +import android.app.servertransaction.WindowTokenClientController; import android.content.Context; import android.content.Intent; import android.content.res.CompatibilityInfo; @@ -70,6 +74,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.content.ReferrerIntent; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -83,7 +88,7 @@ import java.util.function.Consumer; /** * Test for verifying {@link android.app.ActivityThread} class. * Build/Install/Run: - * atest FrameworksCoreTests:android.app.activity.ActivityThreadTest + * atest FrameworksCoreTests:ActivityThreadTest */ @RunWith(AndroidJUnit4.class) @MediumTest @@ -100,14 +105,24 @@ public class ActivityThreadTest { new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); + private WindowTokenClientController mOriginalWindowTokenClientController; + private ArrayList<VirtualDisplay> mCreatedVirtualDisplays; + @Before + public void setup() { + // Keep track of the original controller, so that it can be used to restore in tearDown() + // when there is override in some test cases. + mOriginalWindowTokenClientController = WindowTokenClientController.getInstance(); + } + @After public void tearDown() { if (mCreatedVirtualDisplays != null) { mCreatedVirtualDisplays.forEach(VirtualDisplay::release); mCreatedVirtualDisplays = null; } + WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); } @Test @@ -730,6 +745,39 @@ public class ActivityThreadTest { assertFalse(activity.enterPipSkipped()); } + @Test + public void testHandleWindowContextConfigurationChanged() { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final ActivityThread activityThread = activity.getActivityThread(); + final WindowTokenClientController windowTokenClientController = + mock(WindowTokenClientController.class); + WindowTokenClientController.overrideForTesting(windowTokenClientController); + final IBinder clientToken = mock(IBinder.class); + final Configuration configuration = new Configuration(); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread + .handleWindowContextConfigurationChanged( + clientToken, configuration, DEFAULT_DISPLAY)); + + verify(windowTokenClientController).onWindowContextConfigurationChanged( + clientToken, configuration, DEFAULT_DISPLAY); + } + + @Test + public void testHandleWindowContextWindowRemoval() { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final ActivityThread activityThread = activity.getActivityThread(); + final WindowTokenClientController windowTokenClientController = + mock(WindowTokenClientController.class); + WindowTokenClientController.overrideForTesting(windowTokenClientController); + final IBinder clientToken = mock(IBinder.class); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread + .handleWindowContextWindowRemoval(clientToken)); + + verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken); + } + /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int)} to try to push activity configuration to the activity for the given diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java new file mode 100644 index 000000000000..7811e1a58c22 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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.app.servertransaction; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.content.res.Configuration; +import android.os.IBinder; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowContextConfigurationChangeItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowContextConfigurationChangeItemTest + */ +public class WindowContextConfigurationChangeItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private IBinder mToken; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IBinder mClientToken; + // Can't mock final class. + private final Configuration mConfiguration = new Configuration(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() { + final WindowContextConfigurationChangeItem item = WindowContextConfigurationChangeItem + .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY); + item.execute(mHandler, mToken, mPendingActions); + + verify(mHandler).handleWindowContextConfigurationChanged(mClientToken, mConfiguration, + DEFAULT_DISPLAY); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java new file mode 100644 index 000000000000..2c83c70e9ae2 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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.app.servertransaction; + +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowContextWindowRemovalItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowContextWindowRemovalItemTest + */ +public class WindowContextWindowRemovalItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private IBinder mToken; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IBinder mClientToken; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() { + final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain( + mClientToken); + item.execute(mHandler, mToken, mPendingActions); + + verify(mHandler).handleWindowContextWindowRemoval(mClientToken); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java new file mode 100644 index 000000000000..3b2fe583f605 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2023 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.app.servertransaction; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; +import android.window.WindowTokenClient; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowTokenClientController}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowTokenClientControllerTest + */ +@SmallTest +@Presubmit +public class WindowTokenClientControllerTest { + + @Mock + private IWindowManager mWindowManagerService; + @Mock + private WindowTokenClient mWindowTokenClient; + @Mock + private IBinder mClientToken; + @Mock + private IBinder mWindowToken; + // Can't mock final class. + private final Configuration mConfiguration = new Configuration(); + + private IWindowManager mOriginalWindowManagerService; + + private WindowTokenClientController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mOriginalWindowManagerService = WindowManagerGlobal.getWindowManagerService(); + WindowManagerGlobal.overrideWindowManagerServiceForTesting(mWindowManagerService); + doReturn(mClientToken).when(mWindowTokenClient).asBinder(); + mController = spy(WindowTokenClientController.getInstance()); + } + + @After + public void tearDown() { + WindowManagerGlobal.overrideWindowManagerServiceForTesting(mOriginalWindowManagerService); + } + + @Test + public void testAttachToDisplayArea() throws RemoteException { + doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + + assertFalse(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */)); + verify(mWindowManagerService).attachWindowContextToDisplayArea(mWindowTokenClient, + TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, null /* options */); + verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean()); + + doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + + assertTrue(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */)); + verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY, + false /* shouldReportConfigChange */); + } + + @Test + public void testAttachToDisplayArea_detachIfNeeded() throws RemoteException { + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService).detachWindowContextFromWindowContainer(any()); + } + + @Test + public void testAttachToDisplayContent() throws RemoteException { + doReturn(null).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + + assertFalse(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY)); + verify(mWindowManagerService).attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY); + verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean()); + + doReturn(mConfiguration).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + + assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY)); + verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY, + false /* shouldReportConfigChange */); + } + + @Test + public void testAttachToDisplayContent_detachIfNeeded() throws RemoteException { + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(null).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(mConfiguration).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService).detachWindowContextFromWindowContainer(any()); + } + + @Test + public void testAttachToWindowToken() throws RemoteException { + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + + verify(mWindowManagerService).attachWindowContextToWindowToken(mWindowTokenClient, + mWindowToken); + } + + @Test + public void testAttachToWindowToken_detachIfNeeded() throws RemoteException { + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService).detachWindowContextFromWindowContainer(any()); + } + + @Test + public void testOnWindowContextConfigurationChanged() { + mController.onWindowContextConfigurationChanged( + mClientToken, mConfiguration, DEFAULT_DISPLAY); + + verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt()); + + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + + mController.onWindowContextConfigurationChanged( + mClientToken, mConfiguration, DEFAULT_DISPLAY); + + verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY); + } + + @Test + public void testOnWindowContextWindowRemoved() { + mController.onWindowContextWindowRemoved(mClientToken); + + verify(mWindowTokenClient, never()).onWindowTokenRemoved(); + + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + + mController.onWindowContextWindowRemoved(mClientToken); + + verify(mWindowTokenClient).onWindowTokenRemoved(); + } +} diff --git a/core/tests/coretests/src/android/content/TEST_MAPPING b/core/tests/coretests/src/android/content/TEST_MAPPING new file mode 100644 index 000000000000..bbc2458f5d8b --- /dev/null +++ b/core/tests/coretests/src/android/content/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.content.ContentCaptureOptionsTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING new file mode 100644 index 000000000000..f8beac2814db --- /dev/null +++ b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.view.contentcapture" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING new file mode 100644 index 000000000000..3cd4e17d820b --- /dev/null +++ b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.view.contentprotection" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index a52d2e88145f..5f2aecc40d16 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -24,17 +24,20 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.app.servertransaction.WindowTokenClientController; import android.os.Binder; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,14 +59,26 @@ import org.mockito.MockitoAnnotations; public class WindowContextControllerTest { private WindowContextController mController; @Mock + private WindowTokenClientController mWindowTokenClientController; + @Mock private WindowTokenClient mMockToken; + private WindowTokenClientController mOriginalController; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mController = new WindowContextController(mMockToken); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); - doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any()); + mOriginalController = WindowTokenClientController.getInstance(); + WindowTokenClientController.overrideForTesting(mWindowTokenClientController); + doReturn(true).when(mWindowTokenClientController).attachToDisplayArea( + eq(mMockToken), anyInt(), anyInt(), any()); + } + + @After + public void tearDown() { + WindowTokenClientController.overrideForTesting(mOriginalController); } @Test(expected = IllegalStateException.class) @@ -78,7 +93,7 @@ public class WindowContextControllerTest { public void testDetachIfNeeded_NotAttachedYet_DoNothing() { mController.detachIfNeeded(); - verify(mMockToken, never()).detachFromWindowContainerIfNeeded(); + verify(mWindowTokenClientController, never()).detachIfNeeded(any()); } @Test diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index a605e2bdd3d6..8bd500eac67b 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -166,7 +166,7 @@ android_library { // *.kt sources are inside a filegroup. "kotlin-annotations", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], manifest: "AndroidManifest.xml", plugins: ["dagger2-compiler"], } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt index d5d072a8d449..122dcbb3c2ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt @@ -94,7 +94,6 @@ class FloatingContentCoordinator constructor() { * non-overlapping. * @return The new bounds for this content. */ - @JvmDefault fun calculateNewBoundsOnOverlap( overlappingContentBounds: Rect, otherContentBounds: List<Rect> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 711df0d89936..c05af73e6765 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -368,7 +368,6 @@ class DesktopModeTaskRepository { /** * Called when the active tasks change in desktop mode. */ - @JvmDefault fun onActiveTasksChanged(displayId: Int) {} } @@ -379,17 +378,15 @@ class DesktopModeTaskRepository { /** * Called when the desktop starts or stops showing freeform tasks. */ - @JvmDefault fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {} /** * Called when the desktop stashed status changes. */ - @JvmDefault fun onStashedChanged(displayId: Int, stashed: Boolean) {} } } private fun <T> Iterable<T>.toDumpString(): String { return joinToString(separator = ", ", prefix = "[", postfix = "]") -}
\ No newline at end of file +} 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 af8ef174b168..7699b4bfd13a 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 @@ -737,12 +737,23 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Intent fillInIntent2 = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2); + final ActivityOptions activityOptions1 = options1 != null + ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic(); + final ActivityOptions activityOptions2 = options2 != null + ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic(); if (samePackage(packageName1, packageName2, userId1, userId2)) { if (supportMultiInstancesSplit(packageName1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); fillInIntent2 = new Intent(); fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + + if (shortcutInfo1 != null) { + activityOptions1.setApplyMultipleTaskFlagForShortcut(true); + } + if (shortcutInfo2 != null) { + activityOptions2.setApplyMultipleTaskFlagForShortcut(true); + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { pendingIntent2 = null; @@ -754,9 +765,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Toast.LENGTH_SHORT).show(); } } - mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1, - pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio, - remoteTransition, instanceId); + mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, + activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2, + activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition, + instanceId); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index e52fd00e7df7..dc78c9b139f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -407,7 +407,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); } // Rotation change of independent non display window container. - if (change.getParent() == null + if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY) && change.getStartRotation() != change.getEndRotation()) { startRotationAnimation(startTransaction, change, info, ROTATION_ANIMATION_ROTATE, animations, onAnimFinish); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index a242c72db8b3..c22cc6fbea8f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -186,9 +186,12 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget); - final IRemoteTransition remote = remoteTransition.getRemoteTransition(); + if (remoteTransition == null) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Merge into remote: %s", remoteTransition); + + final IRemoteTransition remote = remoteTransition.getRemoteTransition(); if (remote == null) return; IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index 2f7a25ea586d..97a717bd403a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -107,4 +108,10 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : Assume.assumeFalse(flicker.scenario.isGesturalNavigation) super.focusChanges() } + + @FlakyTest(bugId = 289943985) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index ad4d97f6fe40..38e9f390835c 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -61,7 +61,7 @@ android_test { "libstaticjvmtiagent", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], plugins: ["dagger2-compiler"], diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 7ef82a7ff6e5..ffc664c2e1bc 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -61,18 +61,10 @@ namespace uirenderer { ADD_FAILURE() << "ClipState not a rect"; \ } -#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \ - TEST(test_case_name, test_name##_##pipeline) { \ - RenderPipelineType oldType = Properties::getRenderPipelineType(); \ - Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \ - functionCall; \ - Properties::overrideRenderPipelineType(oldType); \ - }; - -#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \ - INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \ - TestUtils::runOnRenderThread( \ - test_case_name##_##test_name##_RenderThreadTest::doTheThing)) +#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name) \ + TEST(test_case_name, test_name) { \ + TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \ + } /** * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope @@ -83,21 +75,7 @@ namespace uirenderer { public: \ static void doTheThing(renderthread::RenderThread& renderThread); \ }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ - /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ - /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ - void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ - renderthread::RenderThread& renderThread) - -/** - * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes - */ -#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_RenderThreadTest { \ - public: \ - static void doTheThing(renderthread::RenderThread& renderThread); \ - }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ + INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name); \ /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp index cc7d34b3994e..89d00d3c0dcb 100644 --- a/libs/hwui/tests/unit/CacheManagerTests.cpp +++ b/libs/hwui/tests/unit/CacheManagerTests.cpp @@ -35,7 +35,7 @@ static size_t getCacheUsage(GrDirectContext* grContext) { } // TOOD(258700630): fix this test and re-enable -RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) { +RENDERTHREAD_TEST(CacheManager, DISABLED_trimMemory) { int32_t width = DeviceInfo::get()->getWidth(); int32_t height = DeviceInfo::get()->getHeight(); GrDirectContext* grContext = renderThread.getGrContext(); diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 1e055c26afc5..073a8357e574 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -355,7 +355,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) { EXPECT_EQ(3, canvas.getIndex()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { +RENDERTHREAD_TEST(RenderNodeDrawable, emptyReceiver) { class ProjectionTestCanvas : public SkCanvas { public: ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {} @@ -419,7 +419,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { EXPECT_EQ(2, canvas.getDrawCounter()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) { +RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { /* R is backward projected on B and C is a layer. A / \ @@ -1052,7 +1052,7 @@ TEST(RenderNodeDrawable, renderNode) { } // Verify that layers are composed with linear filtering. -RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { +RENDERTHREAD_TEST(RenderNodeDrawable, layerComposeQuality) { static const int CANVAS_WIDTH = 1; static const int CANVAS_HEIGHT = 1; static const int LAYER_WIDTH = 1; @@ -1170,7 +1170,7 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) { } // Draw a vector drawable twice but with different bounds and verify correct bounds are used. -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { +RENDERTHREAD_TEST(SkiaRecordingCanvas, drawVectorDrawable) { static const int CANVAS_WIDTH = 100; static const int CANVAS_HEIGHT = 200; class VectorDrawableTestCanvas : public TestCanvasBase { diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 9aa2e1db4461..0f8bd1368f5a 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -370,9 +370,9 @@ TEST(ShaderCacheTest, testCacheValidation) { } using namespace android::uirenderer; -RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) { +RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) { if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) { - // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants. + // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants. GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan"; } if (!folderExist(getExternalStorageFolder())) { diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index 1a1ce1e9cf66..f6be7b20a9e2 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -136,7 +136,7 @@ public: } }; -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { +RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( @@ -195,7 +195,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { canvasContext->destroy(); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) { +RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 6f180e7498cb..3ded540c3152 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -42,7 +42,7 @@ using namespace android::uirenderer; using namespace android::uirenderer::renderthread; using namespace android::uirenderer::skiapipeline; -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { +RENDERTHREAD_TEST(SkiaPipeline, renderFrame) { auto redNode = TestUtils::createSkiaNode( 0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); @@ -62,7 +62,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) { auto halfGreenNode = TestUtils::createSkiaNode( 0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) { Paint greenPaint; @@ -89,7 +89,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { auto redNode = TestUtils::createSkiaNode( 0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); @@ -111,7 +111,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) { +RENDERTHREAD_TEST(SkiaPipeline, renderLayer) { auto redNode = TestUtils::createSkiaNode( 0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); @@ -154,7 +154,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) { blueNode->setLayerSurface(sk_sp<SkSurface>()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) { +RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) { ScopedProperty<bool> prop(Properties::debugOverdraw, true); auto whiteNode = TestUtils::createSkiaNode( @@ -227,7 +227,7 @@ public: }; } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) { +RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) { class DeferTestCanvas : public SkCanvas { public: DeferTestCanvas() : SkCanvas(800, 600) {} @@ -297,7 +297,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) { EXPECT_EQ(4, surface->canvas()->mDrawCounter); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) { +RENDERTHREAD_TEST(SkiaPipeline, clipped) { static const int CANVAS_WIDTH = 200; static const int CANVAS_HEIGHT = 200; class ClippedTestCanvas : public SkCanvas { @@ -330,7 +330,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) { } // Test renderFrame with a dirty clip and a pre-transform matrix. -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) { +RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) { static const int CANVAS_WIDTH = 200; static const int CANVAS_HEIGHT = 100; static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1); @@ -366,7 +366,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) { EXPECT_EQ(1, surface->canvas()->mDrawCounter); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) { +RENDERTHREAD_TEST(SkiaPipeline, clip_replace) { static const int CANVAS_WIDTH = 50; static const int CANVAS_HEIGHT = 50; class ClipReplaceTestCanvas : public SkCanvas { @@ -396,7 +396,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) { EXPECT_EQ(1, surface->canvas()->mDrawCounter); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { +RENDERTHREAD_TEST(SkiaPipeline, context_lost) { test::TestContext context; auto surface = context.surface(); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); @@ -410,7 +410,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { EXPECT_TRUE(pipeline->isSurfaceReady()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) { +RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) { // create a pipeline and add a picture callback auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); int callbackCount = 0; diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp index 10c874ec3f12..76cbc8abc808 100644 --- a/libs/hwui/tests/unit/main.cpp +++ b/libs/hwui/tests/unit/main.cpp @@ -14,15 +14,15 @@ * limitations under the License. */ -#include "gmock/gmock.h" -#include "gtest/gtest.h" +#include <getopt.h> +#include <signal.h> #include "Properties.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" #include "hwui/Typeface.h" #include "tests/common/LeakChecker.h" -#include <signal.h> - using namespace std; using namespace android; using namespace android::uirenderer; @@ -45,6 +45,57 @@ static void gtestSigHandler(int sig, siginfo_t* siginfo, void* context) { raise(sig); } +// For options that only exist in long-form. Anything in the +// 0-255 range is reserved for short options (which just use their ASCII value) +namespace LongOpts { +enum { + Reserved = 255, + Renderer, +}; +} + +static const struct option LONG_OPTIONS[] = { + {"renderer", required_argument, nullptr, LongOpts::Renderer}, {0, 0, 0, 0}}; + +static RenderPipelineType parseRenderer(const char* renderer) { + // Anything that's not skiavk is skiagl + if (!strcmp(renderer, "skiavk")) { + return RenderPipelineType::SkiaVulkan; + } + return RenderPipelineType::SkiaGL; +} + +struct Options { + RenderPipelineType renderer = RenderPipelineType::SkiaGL; +}; + +Options parseOptions(int argc, char* argv[]) { + int c; + opterr = 0; + Options opts; + + while (true) { + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long(argc, argv, "", LONG_OPTIONS, &option_index); + + if (c == -1) break; + + switch (c) { + case 0: + // Option set a flag, don't need to do anything + // (although none of the current LONG_OPTIONS do this...) + break; + + case LongOpts::Renderer: + opts.renderer = parseRenderer(optarg); + break; + } + } + return opts; +} + class TypefaceEnvironment : public testing::Environment { public: virtual void SetUp() { Typeface::setRobotoTypefaceForTest(); } @@ -64,8 +115,9 @@ int main(int argc, char* argv[]) { // Avoid talking to SF Properties::isolatedProcess = true; - // Default to GLES (Vulkan-aware tests will override this) - Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL); + + auto opts = parseOptions(argc, argv); + Properties::overrideRenderPipelineType(opts.renderer); // Run the tests testing::InitGoogleTest(&argc, argv); diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS index 6d6a9f8eb98c..bbe5e06bb282 100644 --- a/media/java/android/media/OWNERS +++ b/media/java/android/media/OWNERS @@ -10,5 +10,8 @@ include platform/frameworks/av:/media/janitors/media_solutions_OWNERS per-file *Image* = file:/graphics/java/android/graphics/OWNERS +per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=set noparent +per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS + # Haptics team also works on Ringtone per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS diff --git a/media/jni/OWNERS b/media/jni/OWNERS index 96894d15d8b1..e12d828733fa 100644 --- a/media/jni/OWNERS +++ b/media/jni/OWNERS @@ -3,3 +3,6 @@ per-file android_mtp_*.cpp=aprasath@google.com,anothermark@google.com,kumarashis # extra for TV related files per-file android_media_tv_*=hgchen@google.com,quxiangfang@google.com + +per-file android_media_JetPlayer.cpp,android_media_MediaDataSource.cpp,android_media_MediaDataSource.h,android_media_MediaPlayer.java=set noparent +per-file android_media_JetPlayer.cpp,android_media_MediaDataSource.cpp,android_media_MediaDataSource.h,android_media_MediaPlayer.java=file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index dea7f03d369a..5ea98c0c6700 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -2615,7 +2615,7 @@ static void android_media_MediaCodec_native_queueLinearBlock( return; } auto cryptoInfo = - cryptoInfoObj ? NativeCryptoInfo{size} : NativeCryptoInfo{env, cryptoInfoObj}; + cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{size}; if (env->ExceptionCheck()) { // Creation of cryptoInfo failed. Let the exception bubble up. return; diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp index 28b9bc04a249..fe26dc3d7feb 100644 --- a/packages/CredentialManager/Android.bp +++ b/packages/CredentialManager/Android.bp @@ -44,7 +44,7 @@ android_app { platform_apis: true, privileged: true, - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], optimize: { proguard_compatibility: false, diff --git a/packages/EasterEgg/Android.bp b/packages/EasterEgg/Android.bp index 8699f59e4fba..0caf505c2177 100644 --- a/packages/EasterEgg/Android.bp +++ b/packages/EasterEgg/Android.bp @@ -70,5 +70,5 @@ android_app { manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java index 1d433e767e5b..5bc271954b25 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java @@ -59,7 +59,7 @@ public abstract class AbstractWifiMacAddressPreferenceController @Override public boolean isAvailable() { - return true; + return mWifiManager != null; } @Override diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7be60431b91b..0f16d930dcf7 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -203,7 +203,7 @@ android_library { manifest: "AndroidManifest.xml", javacflags: ["-Adagger.fastInit=enabled"], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], plugins: ["dagger2-compiler"], @@ -394,7 +394,7 @@ android_library { "android.test.base", "android.test.mock", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], aaptflags: [ "--extra-packages", "com.android.systemui", @@ -516,7 +516,7 @@ android_app { certificate: "platform", privileged: true, - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], dxflags: ["--multi-dex"], optimize: { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 764a8556a54d..8306620b3de6 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -302,10 +302,9 @@ class ActivityLaunchAnimator( interface Callback { /** Whether we are currently on the keyguard or not. */ - @JvmDefault fun isOnKeyguard(): Boolean = false + fun isOnKeyguard(): Boolean = false /** Hide the keyguard and animate using [runner]. */ - @JvmDefault fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) { throw UnsupportedOperationException() } @@ -316,16 +315,16 @@ class ActivityLaunchAnimator( interface Listener { /** Called when an activity launch animation started. */ - @JvmDefault fun onLaunchAnimationStart() {} + fun onLaunchAnimationStart() {} /** * Called when an activity launch animation is finished. This will be called if and only if * [onLaunchAnimationStart] was called earlier. */ - @JvmDefault fun onLaunchAnimationEnd() {} + fun onLaunchAnimationEnd() {} /** Called when an activity launch animation made progress. */ - @JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {} + fun onLaunchAnimationProgress(linearProgress: Float) {} } /** diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp index 6119e96e5bac..06d94ac5400e 100644 --- a/packages/SystemUI/compose/core/tests/Android.bp +++ b/packages/SystemUI/compose/core/tests/Android.bp @@ -44,5 +44,5 @@ android_test { "androidx.compose.ui_ui-test-manifest", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp index ff534bd01fd3..c7c9140b53ed 100644 --- a/packages/SystemUI/compose/features/tests/Android.bp +++ b/packages/SystemUI/compose/features/tests/Android.bp @@ -44,5 +44,5 @@ android_test { "androidx.compose.ui_ui-test-manifest", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp index dc450bb71dfe..fc37b355494f 100644 --- a/packages/SystemUI/customization/Android.bp +++ b/packages/SystemUI/customization/Android.bp @@ -48,5 +48,5 @@ android_library { ], min_sdk_version: "current", plugins: ["dagger2-compiler"], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 9c864abca9dd..a38c629b7741 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -366,10 +366,16 @@ <!-- Whether or not notifications that can be expanded will always be in their expanded state. This value only affects notifications that are not a group of notifications from the same - applications. If this value is false, then only the first notification will be expanded; - the other notifications need to be manually expanded by the user. --> + applications. If this value is false, then only the first notification will be expanded + when config_autoExpandFirstNotification is true; the other notifications need to be + manually expanded by the user. --> <bool name="config_alwaysExpandNonGroupedNotifications">false</bool> + <!-- Whether or not the first expandable notification will be expanded automatically by the + system. This value only affects notifications that are not a group of notifications from + the same applications and when config_alwaysExpandNonGroupedNotifications is false. --> + <bool name="config_autoExpandFirstNotification">true</bool> + <!-- Whether or not an expandable notification can be manually expanded or collapsed by the user. Grouped notifications are still expandable even if this value is false. --> <bool name="config_enableNonGroupedNotificationExpand">true</bool> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 28e786b71874..ca30e159a0ff 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -65,7 +65,7 @@ android_library { ], min_sdk_version: "current", plugins: ["dagger2-compiler"], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } java_library { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 35cf4a1ecf0a..35624770b712 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -43,8 +43,6 @@ import com.android.systemui.rotationlock.RotationLockModule; import com.android.systemui.screenshot.ReferenceScreenshotModule; import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.shade.ShadeControllerImpl; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyboardShortcutsModule; @@ -150,9 +148,6 @@ public abstract class ReferenceSystemUIModule { @Binds abstract DockManager bindDockManager(DockManagerImpl dockManager); - @Binds - abstract ShadeController provideShadeController(ShadeControllerImpl shadeController); - @SysUISingleton @Provides @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 0670ec380861..79a1728470dc 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -254,7 +254,7 @@ object Flags { /** Migrate the indication area to the new keyguard root view. */ // TODO(b/280067944): Tracking bug. @JvmField - val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true) + val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area") /** * Migrate the bottom area to the new keyguard root view. @@ -294,6 +294,11 @@ object Flags { @JvmField val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl") + /** Migrate the status view from the notification panel to keyguard root view. */ + // TODO(b/291767565): Tracking bug. + @JvmField + val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") diff --git a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt new file mode 100644 index 000000000000..eaecda52a5a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 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.flags + +import android.util.Log +import com.android.systemui.Dependency + +/** + * This class promotes best practices for flag guarding System UI view refactors. + * * [isEnabled] allows changing an implementation. + * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and + * ensure that it is not being invoked accidentally in the post-flag refactor. + * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on + * flag-disabled builds, but with a check that should crash eng builds or tests when the + * expectation is violated. + * + * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it, + * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes + * inside views where injecting flag values after initialization can be error-prone. + */ +class ViewRefactorFlag +private constructor( + private val injectedFlags: FeatureFlags?, + private val flag: BooleanFlag, + private val readFlagValue: (FeatureFlags) -> Boolean +) { + @JvmOverloads + constructor( + flags: FeatureFlags? = null, + flag: UnreleasedFlag + ) : this(flags, flag, { it.isEnabled(flag) }) + + @JvmOverloads + constructor( + flags: FeatureFlags? = null, + flag: ReleasedFlag + ) : this(flags, flag, { it.isEnabled(flag) }) + + /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */ + val isEnabled by lazy { + @Suppress("DEPRECATION") + val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java) + readFlagValue(featureFlags) + } + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + * + * Example usage: + * ``` + * public void setController(NotificationShelfController notificationShelfController) { + * mShelfRefactor.assertDisabled(); + * mController = notificationShelfController; + * } + * ```` + */ + fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." } + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + * + * Example usage: + * ``` + * public void setShelfIcons(NotificationIconContainer icons) { + * if (mShelfRefactor.expectEnabled()) { + * mShelfIcons = icons; + * } + * } + * ``` + */ + fun expectEnabled(): Boolean { + if (!isEnabled) { + val message = "Code path not supported when $flag is disabled." + Log.wtf(TAG, message, Exception(message)) + } + return isEnabled + } + + private companion object { + private const val TAG = "ViewRefactorFlag" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 364614421567..489d2ab4d342 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -171,7 +171,6 @@ class KeyguardUnlockAnimationController @Inject constructor( * for the canned animation (if applicable) so interested parties can sync with it. If no * canned animation is playing, these are both 0. */ - @JvmDefault fun onUnlockAnimationStarted( playingCannedAnimation: Boolean, isWakeAndUnlockNotFromDream: Boolean, @@ -184,7 +183,6 @@ class KeyguardUnlockAnimationController @Inject constructor( * The keyguard is no longer visible in this state and the app/launcher behind the keyguard * is now completely visible. */ - @JvmDefault fun onUnlockAnimationFinished() {} } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index ddd4a2bf4453..e0834bb894b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2982,6 +2982,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void onKeyguardExitFinished() { + if (DEBUG) Log.d(TAG, "onKeyguardExitFinished()"); // only play "unlock" noises if not on a call (since the incall UI // disables the keyguard) if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { @@ -3203,13 +3204,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, flags |= StatusBarManager.DISABLE_RECENT; } - if (mPowerGestureIntercepted) { + if (mPowerGestureIntercepted && mOccluded && isSecure()) { flags |= StatusBarManager.DISABLE_RECENT; } if (DEBUG) { Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons + + " mPowerGestureIntercepted=" + mPowerGestureIntercepted + " --> flags=0x" + Integer.toHexString(flags)); } @@ -3437,6 +3439,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, pw.print(" mPendingLock: "); pw.println(mPendingLock); pw.print(" wakeAndUnlocking: "); pw.println(mWakeAndUnlocking); pw.print(" mPendingPinLock: "); pw.println(mPendingPinLock); + pw.print(" mPowerGestureIntercepted: "); pw.println(mPowerGestureIntercepted); } /** diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt index d652889f0082..d949a2a0afe5 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt @@ -145,13 +145,10 @@ class PrivacyConfig @Inject constructor( } interface Callback { - @JvmDefault fun onFlagMicCameraChanged(flag: Boolean) {} - @JvmDefault fun onFlagLocationChanged(flag: Boolean) {} - @JvmDefault fun onFlagMediaProjectionChanged(flag: Boolean) {} } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index a676150f44a2..eb8ef9bf3e50 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -216,7 +216,6 @@ class PrivacyItemController @Inject constructor( interface Callback : PrivacyConfig.Callback { fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) - @JvmDefault fun onFlagAllChanged(flag: Boolean) {} } diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt index 995c6a476f0d..33c47cc082e1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt @@ -26,7 +26,7 @@ import java.util.concurrent.Executor import javax.inject.Inject import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER +import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER import com.android.systemui.statusbar.policy.DeviceProvisionedController import javax.inject.Named diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt index bb7f721ad61f..468a75d8276e 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt @@ -52,12 +52,12 @@ interface DisplayTracker { interface Callback { /** Notifies that a display has been added. */ - @JvmDefault fun onDisplayAdded(displayId: Int) {} + fun onDisplayAdded(displayId: Int) {} /** Notifies that a display has been removed. */ - @JvmDefault fun onDisplayRemoved(displayId: Int) {} + fun onDisplayRemoved(displayId: Int) {} /** Notifies a display has been changed */ - @JvmDefault fun onDisplayChanged(displayId: Int) {} + fun onDisplayChanged(displayId: Int) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 33a3125d1c68..93a3e905f0e0 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -71,7 +71,6 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { * Same as {@link onUserChanging(Int, Context, CountDownLatch)} but the latch will be * auto-decremented after the completion of this method. */ - @JvmDefault fun onUserChanging(newUser: Int, userContext: Context) {} /** @@ -82,7 +81,6 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { * user switch duration. When overriding this method, countDown() MUST be called on the * latch once execution is complete. */ - @JvmDefault fun onUserChanging(newUser: Int, userContext: Context, latch: CountDownLatch) { onUserChanging(newUser, userContext) latch.countDown() @@ -93,13 +91,11 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { * Override this method to run things after the screen is unfrozen for the user switch. * Please see {@link #onUserChanging} if you need to hide jank. */ - @JvmDefault fun onUserChanged(newUser: Int, userContext: Context) {} /** * Notifies that the current user's profiles have changed. */ - @JvmDefault fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt new file mode 100644 index 000000000000..7a803867d4a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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.shade + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module + +/** Fulfills dependencies on the shade with empty implementations for variants with no shade. */ +@Module +abstract class ShadeEmptyImplModule { + @Binds + @SysUISingleton + abstract fun bindsShadeViewController(svc: ShadeViewControllerEmptyImpl): ShadeViewController + + @Binds + @SysUISingleton + abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 8b89ff49f418..529f12e0658e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -54,7 +54,7 @@ import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_H import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT -import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER +import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER import com.android.systemui.shade.carrier.ShadeCarrierGroup import com.android.systemui.shade.carrier.ShadeCarrierGroupController import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index be21df1139ad..89aaaafbcdf3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -16,293 +16,21 @@ package com.android.systemui.shade -import android.annotation.SuppressLint -import android.content.ContentResolver -import android.os.Handler -import android.view.LayoutInflater -import android.view.ViewStub -import androidx.constraintlayout.motion.widget.MotionLayout -import com.android.keyguard.LockIconView -import com.android.systemui.CoreStartable -import com.android.systemui.R -import com.android.systemui.battery.BatteryMeterView -import com.android.systemui.battery.BatteryMeterViewController -import com.android.systemui.biometrics.AuthRippleController -import com.android.systemui.biometrics.AuthRippleView -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.privacy.OngoingPrivacyChip -import com.android.systemui.scene.ui.view.WindowRootView -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.LightRevealScrim -import com.android.systemui.statusbar.NotificationShelf -import com.android.systemui.statusbar.NotificationShelfController -import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent -import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout -import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView -import com.android.systemui.statusbar.phone.StatusBarLocation -import com.android.systemui.statusbar.phone.StatusIconContainer -import com.android.systemui.statusbar.phone.TapAgainView -import com.android.systemui.statusbar.policy.BatteryController -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.tuner.TunerService + import dagger.Binds import dagger.Module -import dagger.Provides -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap -import javax.inject.Named -import javax.inject.Provider /** Module for classes related to the notification shade. */ -@Module(includes = [StartShadeModule::class]) +@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class]) abstract class ShadeModule { - - @Binds - @IntoMap - @ClassKey(AuthRippleController::class) - abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable - @Binds @SysUISingleton abstract fun bindsShadeViewController( notificationPanelViewController: NotificationPanelViewController ): ShadeViewController - companion object { - const val SHADE_HEADER = "large_screen_shade_header" - - @SuppressLint("InflateParams") // Root views don't have parents. - @Provides - @SysUISingleton - fun providesWindowRootView( - layoutInflater: LayoutInflater, - featureFlags: FeatureFlags, - ): WindowRootView { - return if ( - featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable() - ) { - layoutInflater.inflate(R.layout.scene_window_root, null) - } else { - layoutInflater.inflate(R.layout.super_notification_shade, null) - } - as WindowRootView? - ?: throw IllegalStateException("Window root view could not be properly inflated") - } - - @Provides - @SysUISingleton - // TODO(b/277762009): Do something similar to - // {@link StatusBarWindowModule.InternalWindowView} so that only - // {@link NotificationShadeWindowViewController} can inject this view. - fun providesNotificationShadeWindowView( - root: WindowRootView, - featureFlags: FeatureFlags, - ): NotificationShadeWindowView { - if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { - return root.findViewById(R.id.legacy_window_root) - } - return root as NotificationShadeWindowView? - ?: throw IllegalStateException("root view not a NotificationShadeWindowView") - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - fun providesNotificationStackScrollLayout( - notificationShadeWindowView: NotificationShadeWindowView, - ): NotificationStackScrollLayout { - return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller) - } - - @Provides - @SysUISingleton - fun providesNotificationShelfController( - featureFlags: FeatureFlags, - newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>, - notificationShelfComponentBuilder: NotificationShelfComponent.Builder, - layoutInflater: LayoutInflater, - notificationStackScrollLayout: NotificationStackScrollLayout, - ): NotificationShelfController { - return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { - newImpl.get() - } else { - val shelfView = - layoutInflater.inflate( - R.layout.status_bar_notification_shelf, - notificationStackScrollLayout, - false - ) as NotificationShelf - val component = - notificationShelfComponentBuilder.notificationShelf(shelfView).build() - val notificationShelfController = component.notificationShelfController - notificationShelfController.init() - notificationShelfController - } - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - fun providesNotificationPanelView( - notificationShadeWindowView: NotificationShadeWindowView, - ): NotificationPanelView { - return notificationShadeWindowView.findViewById(R.id.notification_panel) - } - - /** - * Constructs a new, unattached [KeyguardBottomAreaView]. - * - * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it - */ - @Provides - fun providesKeyguardBottomAreaView( - npv: NotificationPanelView, - layoutInflater: LayoutInflater, - ): KeyguardBottomAreaView { - return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false) - as KeyguardBottomAreaView - } - - @Provides - @SysUISingleton - fun providesLightRevealScrim( - notificationShadeWindowView: NotificationShadeWindowView, - ): LightRevealScrim { - return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim) - } - - @Provides - @SysUISingleton - fun providesKeyguardRootView( - notificationShadeWindowView: NotificationShadeWindowView, - ): KeyguardRootView { - return notificationShadeWindowView.findViewById(R.id.keyguard_root_view) - } - - @Provides - @SysUISingleton - fun providesSharedNotificationContainer( - notificationShadeWindowView: NotificationShadeWindowView, - ): SharedNotificationContainer { - return notificationShadeWindowView.findViewById(R.id.shared_notification_container) - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - fun providesAuthRippleView( - notificationShadeWindowView: NotificationShadeWindowView, - ): AuthRippleView? { - return notificationShadeWindowView.findViewById(R.id.auth_ripple) - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - fun providesLockIconView( - keyguardRootView: KeyguardRootView, - notificationPanelView: NotificationPanelView, - featureFlags: FeatureFlags - ): LockIconView { - if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { - return keyguardRootView.findViewById(R.id.lock_icon_view) - } else { - return notificationPanelView.findViewById(R.id.lock_icon_view) - } - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - fun providesTapAgainView( - notificationPanelView: NotificationPanelView, - ): TapAgainView { - return notificationPanelView.findViewById(R.id.shade_falsing_tap_again) - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - fun providesNotificationsQuickSettingsContainer( - notificationShadeWindowView: NotificationShadeWindowView, - ): NotificationsQuickSettingsContainer { - return notificationShadeWindowView.findViewById(R.id.notification_container_parent) - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - @Named(SHADE_HEADER) - fun providesShadeHeaderView( - notificationShadeWindowView: NotificationShadeWindowView, - ): MotionLayout { - val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub) - val layoutId = R.layout.combined_qs_header - stub.layoutResource = layoutId - return stub.inflate() as MotionLayout - } - - @Provides - @SysUISingleton - fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager { - return CombinedShadeHeadersConstraintManagerImpl - } - - // TODO(b/277762009): Only allow this view's controller to inject the view. See above. - @Provides - @SysUISingleton - @Named(SHADE_HEADER) - fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView { - return view.findViewById(R.id.batteryRemainingIcon) - } - - @Provides - @SysUISingleton - @Named(SHADE_HEADER) - fun providesBatteryMeterViewController( - @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView, - userTracker: UserTracker, - configurationController: ConfigurationController, - tunerService: TunerService, - @Main mainHandler: Handler, - contentResolver: ContentResolver, - batteryController: BatteryController, - ): BatteryMeterViewController { - return BatteryMeterViewController( - batteryMeterView, - StatusBarLocation.QS, - userTracker, - configurationController, - tunerService, - mainHandler, - contentResolver, - batteryController, - ) - } - - @Provides - @SysUISingleton - @Named(SHADE_HEADER) - fun providesOngoingPrivacyChip( - @Named(SHADE_HEADER) header: MotionLayout, - ): OngoingPrivacyChip { - return header.findViewById(R.id.privacy_chip) - } - - @Provides - @SysUISingleton - @Named(SHADE_HEADER) - fun providesStatusIconContainer( - @Named(SHADE_HEADER) header: MotionLayout, - ): StatusIconContainer { - return header.findViewById(R.id.statusIcons) - } - } + @Binds + @SysUISingleton + abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt index 56bb1a6020cf..5804040a8676 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt @@ -29,12 +29,12 @@ interface ShadeStateEvents { interface ShadeStateEventsListener { /** Invoked when the notification panel starts or stops collapsing. */ - @JvmDefault fun onPanelCollapsingChanged(isCollapsing: Boolean) {} + fun onPanelCollapsingChanged(isCollapsing: Boolean) {} /** * Invoked when the notification panel starts or stops launching an [android.app.Activity]. */ - @JvmDefault fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {} + fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {} /** * Invoked when the "expand immediate" attribute changes. @@ -45,6 +45,6 @@ interface ShadeStateEvents { * Another example is when full QS is showing, and we swipe up from the bottom. Instead of * going to QQS, the panel fully collapses. */ - @JvmDefault fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {} + fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt new file mode 100644 index 000000000000..fc6479eb62a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2023 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.shade + +import android.annotation.SuppressLint +import android.content.ContentResolver +import android.os.Handler +import android.view.LayoutInflater +import android.view.ViewStub +import androidx.constraintlayout.motion.widget.MotionLayout +import com.android.keyguard.LockIconView +import com.android.systemui.R +import com.android.systemui.battery.BatteryMeterView +import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.biometrics.AuthRippleView +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.privacy.OngoingPrivacyChip +import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneContainerConfig +import com.android.systemui.scene.shared.model.SceneContainerNames +import com.android.systemui.scene.ui.view.SceneWindowRootView +import com.android.systemui.scene.ui.view.WindowRootView +import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.NotificationShelfController +import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent +import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView +import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.phone.StatusIconContainer +import com.android.systemui.statusbar.phone.TapAgainView +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.tuner.TunerService +import dagger.Module +import dagger.Provides +import javax.inject.Named +import javax.inject.Provider + +/** Module for providing views related to the shade. */ +@Module +abstract class ShadeViewProviderModule { + companion object { + const val SHADE_HEADER = "large_screen_shade_header" + + @SuppressLint("InflateParams") // Root views don't have parents. + @Provides + @SysUISingleton + fun providesWindowRootView( + layoutInflater: LayoutInflater, + featureFlags: FeatureFlags, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + viewModelProvider: Provider<SceneContainerViewModel>, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + containerConfigProvider: Provider<SceneContainerConfig>, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, + ): WindowRootView { + return if ( + featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable() + ) { + val sceneWindowRootView = + layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView + sceneWindowRootView.init( + viewModel = viewModelProvider.get(), + containerConfig = containerConfigProvider.get(), + scenes = scenesProvider.get(), + ) + sceneWindowRootView + } else { + layoutInflater.inflate(R.layout.super_notification_shade, null) + } + as WindowRootView? + ?: throw IllegalStateException("Window root view could not be properly inflated") + } + + @Provides + @SysUISingleton + // TODO(b/277762009): Do something similar to + // {@link StatusBarWindowModule.InternalWindowView} so that only + // {@link NotificationShadeWindowViewController} can inject this view. + fun providesNotificationShadeWindowView( + root: WindowRootView, + featureFlags: FeatureFlags, + ): NotificationShadeWindowView { + if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { + return root.findViewById(R.id.legacy_window_root) + } + return root as NotificationShadeWindowView? + ?: throw IllegalStateException("root view not a NotificationShadeWindowView") + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + fun providesNotificationStackScrollLayout( + notificationShadeWindowView: NotificationShadeWindowView, + ): NotificationStackScrollLayout { + return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller) + } + + @Provides + @SysUISingleton + fun providesNotificationShelfController( + featureFlags: FeatureFlags, + newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>, + notificationShelfComponentBuilder: NotificationShelfComponent.Builder, + layoutInflater: LayoutInflater, + notificationStackScrollLayout: NotificationStackScrollLayout, + ): NotificationShelfController { + return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + newImpl.get() + } else { + val shelfView = + layoutInflater.inflate( + R.layout.status_bar_notification_shelf, + notificationStackScrollLayout, + false + ) as NotificationShelf + val component = + notificationShelfComponentBuilder.notificationShelf(shelfView).build() + val notificationShelfController = component.notificationShelfController + notificationShelfController.init() + notificationShelfController + } + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + fun providesNotificationPanelView( + notificationShadeWindowView: NotificationShadeWindowView, + ): NotificationPanelView { + return notificationShadeWindowView.findViewById(R.id.notification_panel) + } + + /** + * Constructs a new, unattached [KeyguardBottomAreaView]. + * + * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it + */ + @Provides + fun providesKeyguardBottomAreaView( + npv: NotificationPanelView, + layoutInflater: LayoutInflater, + ): KeyguardBottomAreaView { + return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false) + as KeyguardBottomAreaView + } + + @Provides + @SysUISingleton + fun providesLightRevealScrim( + notificationShadeWindowView: NotificationShadeWindowView, + ): LightRevealScrim { + return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim) + } + + @Provides + @SysUISingleton + fun providesKeyguardRootView( + notificationShadeWindowView: NotificationShadeWindowView, + ): KeyguardRootView { + return notificationShadeWindowView.findViewById(R.id.keyguard_root_view) + } + + @Provides + @SysUISingleton + fun providesSharedNotificationContainer( + notificationShadeWindowView: NotificationShadeWindowView, + ): SharedNotificationContainer { + return notificationShadeWindowView.findViewById(R.id.shared_notification_container) + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + fun providesAuthRippleView( + notificationShadeWindowView: NotificationShadeWindowView, + ): AuthRippleView? { + return notificationShadeWindowView.findViewById(R.id.auth_ripple) + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + fun providesLockIconView( + keyguardRootView: KeyguardRootView, + notificationPanelView: NotificationPanelView, + featureFlags: FeatureFlags + ): LockIconView { + if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + return keyguardRootView.findViewById(R.id.lock_icon_view) + } else { + return notificationPanelView.findViewById(R.id.lock_icon_view) + } + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + fun providesTapAgainView( + notificationPanelView: NotificationPanelView, + ): TapAgainView { + return notificationPanelView.findViewById(R.id.shade_falsing_tap_again) + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + fun providesNotificationsQuickSettingsContainer( + notificationShadeWindowView: NotificationShadeWindowView, + ): NotificationsQuickSettingsContainer { + return notificationShadeWindowView.findViewById(R.id.notification_container_parent) + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + @Named(SHADE_HEADER) + fun providesShadeHeaderView( + notificationShadeWindowView: NotificationShadeWindowView, + ): MotionLayout { + val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub) + val layoutId = R.layout.combined_qs_header + stub.layoutResource = layoutId + return stub.inflate() as MotionLayout + } + + @Provides + @SysUISingleton + fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager { + return CombinedShadeHeadersConstraintManagerImpl + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + @Named(SHADE_HEADER) + fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView { + return view.findViewById(R.id.batteryRemainingIcon) + } + + @Provides + @SysUISingleton + @Named(SHADE_HEADER) + fun providesBatteryMeterViewController( + @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView, + userTracker: UserTracker, + configurationController: ConfigurationController, + tunerService: TunerService, + @Main mainHandler: Handler, + contentResolver: ContentResolver, + batteryController: BatteryController, + ): BatteryMeterViewController { + return BatteryMeterViewController( + batteryMeterView, + StatusBarLocation.QS, + userTracker, + configurationController, + tunerService, + mainHandler, + contentResolver, + batteryController, + ) + } + + @Provides + @SysUISingleton + @Named(SHADE_HEADER) + fun providesOngoingPrivacyChip( + @Named(SHADE_HEADER) header: MotionLayout, + ): OngoingPrivacyChip { + return header.findViewById(R.id.privacy_chip) + } + + @Provides + @SysUISingleton + @Named(SHADE_HEADER) + fun providesStatusIconContainer( + @Named(SHADE_HEADER) header: MotionLayout, + ): StatusIconContainer { + return header.findViewById(R.id.statusIcons) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt index c50693c30533..15ec18c528b6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import com.android.systemui.CoreStartable +import com.android.systemui.biometrics.AuthRippleController import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -28,4 +29,9 @@ internal abstract class StartShadeModule { @IntoMap @ClassKey(ShadeController::class) abstract fun bind(shadeController: ShadeController): CoreStartable + + @Binds + @IntoMap + @ClassKey(AuthRippleController::class) + abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java index 4ec5f46e7771..7a989cfe227a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar; import android.view.View; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -52,7 +51,6 @@ public class LegacyNotificationShelfControllerImpl implements NotificationShelfC mActivatableNotificationViewController = activatableNotificationViewController; mKeyguardBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; - mView.setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index fb88a96c38c2..763400b307fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -27,10 +27,12 @@ import android.app.Notification; import android.app.WallpaperManager; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.hardware.display.DisplayManager; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; @@ -41,6 +43,7 @@ import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; +import android.view.Display; import android.view.View; import android.widget.ImageView; @@ -74,11 +77,15 @@ import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import dagger.Lazy; @@ -138,6 +145,14 @@ public class NotificationMediaManager implements Dumpable { private BackDropView mBackdrop; private ImageView mBackdropFront; private ImageView mBackdropBack; + private final Point mTmpDisplaySize = new Point(); + + private final DisplayManager mDisplayManager; + @Nullable + private List<String> mSmallerInternalDisplayUids; + private Display mCurrentDisplay; + + private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable; private final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override @@ -184,7 +199,8 @@ public class NotificationMediaManager implements Dumpable { SysuiColorExtractor colorExtractor, KeyguardStateController keyguardStateController, DumpManager dumpManager, - WallpaperManager wallpaperManager) { + WallpaperManager wallpaperManager, + DisplayManager displayManager) { mContext = context; mMediaArtworkProcessor = mediaArtworkProcessor; mKeyguardBypassController = keyguardBypassController; @@ -200,6 +216,7 @@ public class NotificationMediaManager implements Dumpable { mStatusBarStateController = statusBarStateController; mColorExtractor = colorExtractor; mKeyguardStateController = keyguardStateController; + mDisplayManager = displayManager; mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled(); setupNotifPipeline(); @@ -477,6 +494,48 @@ public class NotificationMediaManager implements Dumpable { } /** + * Notify lockscreen wallpaper drawable the current internal display. + */ + public void onDisplayUpdated(Display display) { + Trace.beginSection("NotificationMediaManager#onDisplayUpdated"); + mCurrentDisplay = display; + if (mWallapperDrawable != null) { + mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays()); + } + Trace.endSection(); + } + + private boolean isOnSmallerInternalDisplays() { + if (mSmallerInternalDisplayUids == null) { + mSmallerInternalDisplayUids = findSmallerInternalDisplayUids(); + } + return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId()); + } + + private List<String> findSmallerInternalDisplayUids() { + if (mSmallerInternalDisplayUids != null) { + return mSmallerInternalDisplayUids; + } + List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays( + DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) + .filter(display -> display.getType() == Display.TYPE_INTERNAL) + .collect(Collectors.toList()); + if (internalDisplays.isEmpty()) { + return List.of(); + } + Display largestDisplay = internalDisplays.stream() + .max(Comparator.comparingInt(this::getRealDisplayArea)) + .orElse(internalDisplays.get(0)); + internalDisplays.remove(largestDisplay); + return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList()); + } + + private int getRealDisplayArea(Display display) { + display.getRealSize(mTmpDisplaySize); + return mTmpDisplaySize.x * mTmpDisplaySize.y; + } + + /** * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. */ public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { @@ -551,7 +610,7 @@ public class NotificationMediaManager implements Dumpable { mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; if (lockWallpaper != null) { artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( - mBackdropBack.getResources(), lockWallpaper); + mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays()); // We're in the SHADE mode on the SIM screen - yet we still need to show // the lockscreen wallpaper in that mode. allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; @@ -611,6 +670,10 @@ public class NotificationMediaManager implements Dumpable { mBackdropBack.setBackgroundColor(0xFFFFFFFF); mBackdropBack.setImageDrawable(new ColorDrawable(c)); } else { + if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) { + mWallapperDrawable = + (LockscreenWallpaper.WallpaperDrawable) artworkDrawable; + } mBackdropBack.setImageDrawable(artworkDrawable); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 0e20df6ecfba..b624115dc5e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -553,7 +553,6 @@ class NotificationShadeDepthController @Inject constructor( */ fun onWallpaperZoomOutChanged(zoomOut: Float) - @JvmDefault fun onBlurRadiusChanged(blurRadius: Int) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 25a1dc6322ba..3f37c60bee8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -24,7 +24,6 @@ import android.content.res.Resources; import android.graphics.Rect; import android.util.AttributeSet; import android.util.IndentingPrintWriter; -import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; @@ -40,6 +39,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; import com.android.systemui.animation.ShadeInterpolation; +import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -95,8 +96,10 @@ public class NotificationShelf extends ActivatableNotificationView implements St private float mCornerAnimationDistance; private NotificationShelfController mController; private float mActualWidth = -1; - private boolean mSensitiveRevealAnimEnabled; - private boolean mShelfRefactorFlagEnabled; + private final ViewRefactorFlag mSensitiveRevealAnim = + new ViewRefactorFlag(Flags.SENSITIVE_REVEAL_ANIM); + private final ViewRefactorFlag mShelfRefactor = + new ViewRefactorFlag(Flags.NOTIFICATION_SHELF_REFACTOR); private boolean mCanModifyColorOfNotifications; private boolean mCanInteract; private NotificationStackScrollLayout mHostLayout; @@ -130,7 +133,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St public void bind(AmbientState ambientState, NotificationStackScrollLayoutController hostLayoutController) { - assertRefactorFlagDisabled(); + mShelfRefactor.assertDisabled(); mAmbientState = ambientState; mHostLayoutController = hostLayoutController; hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { @@ -140,7 +143,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout, NotificationRoundnessManager roundnessManager) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; mAmbientState = ambientState; mHostLayout = hostLayout; mRoundnessManager = roundnessManager; @@ -268,7 +271,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); - if (mSensitiveRevealAnimEnabled && viewState.hidden) { + if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) { // if the shelf is hidden, position it at the end of the stack (plus the clip // padding), such that when it appears animated, it will smoothly move in from the // bottom, without jump cutting any notifications @@ -279,7 +282,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getSpeedBumpIndex() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getSpeedBumpIndex(); } else { return mHostLayoutController.getSpeedBumpIndex(); @@ -413,7 +416,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St expandingAnimated, isLastChild, shelfClipStart); // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount - if ((!mSensitiveRevealAnimEnabled && ((isLastChild && !child.isInShelf()) + if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf()) || backgroundForceHidden)) || aboveShelf) { notificationClipEnd = shelfStart + getIntrinsicHeight(); } else { @@ -462,7 +465,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling. // notificationClipEnd handles the discrepancy between a visible and hidden shelf, // so we use that when on the keyguard (and while animating away) to reduce curling. - final float keyguardSafeShelfStart = !mSensitiveRevealAnimEnabled + final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled() && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart; updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart); } @@ -504,7 +507,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private ExpandableView getHostLayoutChildAt(int index) { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return (ExpandableView) mHostLayout.getChildAt(index); } else { return mHostLayoutController.getChildAt(index); @@ -512,7 +515,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getHostLayoutChildCount() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getChildCount(); } else { return mHostLayoutController.getChildCount(); @@ -520,7 +523,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private boolean canModifyColorOfNotifications() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded(); } else { return mController.canModifyColorOfNotifications(); @@ -583,7 +586,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private boolean isViewAffectedBySwipe(ExpandableView expandableView) { - if (!mShelfRefactorFlagEnabled) { + if (!mShelfRefactor.isEnabled()) { return mHostLayoutController.isViewAffectedBySwipe(expandableView); } else { return mRoundnessManager.isViewAffectedBySwipe(expandableView); @@ -607,7 +610,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private View getHostLayoutTransientView(int index) { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getTransientView(index); } else { return mHostLayoutController.getTransientView(index); @@ -615,7 +618,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getHostLayoutTransientViewCount() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.getTransientViewCount(); } else { return mHostLayoutController.getTransientViewCount(); @@ -961,7 +964,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St @Override public void onStateChanged(int newState) { - assertRefactorFlagDisabled(); + mShelfRefactor.assertDisabled(); mStatusBarState = newState; updateInteractiveness(); } @@ -975,7 +978,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private boolean canInteract() { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mCanInteract; } else { return mStatusBarState == StatusBarState.KEYGUARD; @@ -1018,32 +1021,18 @@ public class NotificationShelf extends ActivatableNotificationView implements St return false; } - private void assertRefactorFlagDisabled() { - if (mShelfRefactorFlagEnabled) { - NotificationShelfController.throwIllegalFlagStateError(false); - } - } - - private boolean checkRefactorFlagEnabled() { - if (!mShelfRefactorFlagEnabled) { - Log.wtf(TAG, - "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled."); - } - return mShelfRefactorFlagEnabled; - } - public void setController(NotificationShelfController notificationShelfController) { - assertRefactorFlagDisabled(); + mShelfRefactor.assertDisabled(); mController = notificationShelfController; } public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; mCanModifyColorOfNotifications = canModifyColorOfNotifications; } public void setCanInteract(boolean canInteract) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; mCanInteract = canInteract; updateInteractiveness(); } @@ -1053,27 +1042,15 @@ public class NotificationShelf extends ActivatableNotificationView implements St } private int getIndexOfViewInHostLayout(ExpandableView child) { - if (mShelfRefactorFlagEnabled) { + if (mShelfRefactor.isEnabled()) { return mHostLayout.indexOfChild(child); } else { return mHostLayoutController.indexOfChild(child); } } - /** - * Set whether the sensitive reveal animation feature flag is enabled - * @param enabled true if enabled - */ - public void setSensitiveRevealAnimEnabled(boolean enabled) { - mSensitiveRevealAnimEnabled = enabled; - } - - public void setRefactorFlagEnabled(boolean enabled) { - mShelfRefactorFlagEnabled = enabled; - } - public void requestRoundnessResetFor(ExpandableView child) { - if (!checkRefactorFlagEnabled()) return; + if (!mShelfRefactor.expectEnabled()) return; child.requestRoundnessReset(SHELF_SCROLL); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt index 1619ddaac85c..8a3e21756c2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt @@ -16,11 +16,8 @@ package com.android.systemui.statusbar -import android.util.Log import android.view.View import android.view.View.OnClickListener -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout @@ -49,29 +46,4 @@ interface NotificationShelfController { /** @see View.setOnClickListener */ fun setOnClickListener(listener: OnClickListener) - - companion object { - @JvmStatic - fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) { - if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { - throwIllegalFlagStateError(expected = false) - } - } - - @JvmStatic - fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean = - featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled -> - if (!enabled) { - Log.wtf("NotifShelf", getErrorMessage(expected = true)) - } - } - - @JvmStatic - fun throwIllegalFlagStateError(expected: Boolean): Nothing = - error(getErrorMessage(expected)) - - private fun getErrorMessage(expected: Boolean): String = - "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " + - if (expected) "disabled" else "enabled" - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt index 599beecb0e00..6be407af581d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt @@ -29,7 +29,6 @@ interface SignalCallback { * * @param wifiIndicators a box type containing enough information to properly draw a Wi-Fi icon */ - @JvmDefault fun setWifiIndicators(wifiIndicators: WifiIndicators) {} /** @@ -42,7 +41,6 @@ interface SignalCallback { * NOTE: phones can have multiple subscriptions, so this [mobileDataIndicators] object should be * indexed based on its [subId][MobileDataIndicators.subId] */ - @JvmDefault fun setMobileDataIndicators(mobileDataIndicators: MobileDataIndicators) {} /** @@ -51,7 +49,6 @@ interface SignalCallback { * * @param subs a [SubscriptionInfo] for each subscription that we know about */ - @JvmDefault fun setSubs(subs: List<@JvmSuppressWildcards SubscriptionInfo>) {} /** @@ -63,7 +60,6 @@ interface SignalCallback { * @param show whether or not to show a "no sim" view * @param simDetected whether any SIM is detected or not */ - @JvmDefault fun setNoSims(show: Boolean, simDetected: Boolean) {} /** @@ -72,7 +68,6 @@ interface SignalCallback { * * @param icon an [IconState] for the current ethernet status */ - @JvmDefault fun setEthernetIndicators(icon: IconState) {} /** @@ -80,7 +75,6 @@ interface SignalCallback { * * @param icon an [IconState] for the current airplane mode status */ - @JvmDefault fun setIsAirplaneMode(icon: IconState) {} /** @@ -88,7 +82,6 @@ interface SignalCallback { * * @param enabled the current mobile data feature ennabled state */ - @JvmDefault fun setMobileDataEnabled(enabled: Boolean) {} /** @@ -97,7 +90,6 @@ interface SignalCallback { * @param noValidatedNetwork whether there is any validated network. * @param noNetworksAvailable whether there is any WiFi networks available. */ - @JvmDefault fun setConnectivityStatus( noDefaultNetwork: Boolean, noValidatedNetwork: Boolean, @@ -109,7 +101,6 @@ interface SignalCallback { * @param statusIcon the icon for the call indicator * @param subId subscription ID for which to update the UI */ - @JvmDefault fun setCallIndicator(statusIcon: IconState, subId: Int) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index e5ba3ce1fdae..1c7a1860dbb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.dagger; import android.app.IActivityManager; import android.app.WallpaperManager; import android.content.Context; +import android.hardware.display.DisplayManager; import android.os.RemoteException; import android.service.dreams.IDreamManager; import android.util.Log; @@ -146,7 +147,8 @@ public interface CentralSurfacesDependenciesModule { SysuiColorExtractor colorExtractor, KeyguardStateController keyguardStateController, DumpManager dumpManager, - WallpaperManager wallpaperManager) { + WallpaperManager wallpaperManager, + DisplayManager displayManager) { return new NotificationMediaManager( context, centralSurfacesOptionalLazy, @@ -162,7 +164,8 @@ public interface CentralSurfacesDependenciesModule { colorExtractor, keyguardStateController, dumpManager, - wallpaperManager); + wallpaperManager, + displayManager); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt index 2a18f1f51ace..ef9089099a86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -49,11 +49,10 @@ interface SystemStatusAnimationCallback { fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator? { return null } // Best method name, change my mind - @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(contentDescription: String?): Animator? { return null } - @JvmDefault fun onHidePersistentDot(): Animator? { return null } + fun onHidePersistentDot(): Animator? { return null } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 9ba219903272..8d1e8d0ab524 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -613,20 +613,20 @@ constructor( interface WakeUpListener { /** Called whenever the notifications are fully hidden or shown */ - @JvmDefault fun onFullyHiddenChanged(isFullyHidden: Boolean) {} + fun onFullyHiddenChanged(isFullyHidden: Boolean) {} /** * Called whenever the pulseExpansion changes * * @param expandingChanged if the user has started or stopped expanding */ - @JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {} + fun onPulseExpansionChanged(expandingChanged: Boolean) {} /** * Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running * after the start delay, or after it ends/is cancelled. */ - @JvmDefault fun onDelayedDozeAmountAnimationRunning(running: Boolean) {} + fun onDelayedDozeAmountAnimationRunning(running: Boolean) {} } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt index 1cf9c1e1f299..8029f48152f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt @@ -3,10 +3,10 @@ package com.android.systemui.statusbar.notification import android.util.FloatProperty import android.view.View import androidx.annotation.FloatRange -import com.android.systemui.Dependency import com.android.systemui.R import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.flags.ViewRefactorFlag import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import kotlin.math.abs @@ -27,37 +27,31 @@ interface Roundable { /** Current top roundness */ @get:FloatRange(from = 0.0, to = 1.0) - @JvmDefault val topRoundness: Float get() = roundableState.topRoundness /** Current bottom roundness */ @get:FloatRange(from = 0.0, to = 1.0) - @JvmDefault val bottomRoundness: Float get() = roundableState.bottomRoundness /** Max radius in pixel */ - @JvmDefault val maxRadius: Float get() = roundableState.maxRadius /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */ - @JvmDefault val topCornerRadius: Float get() = - if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.topCornerRadius + if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius else topRoundness * maxRadius /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */ - @JvmDefault val bottomCornerRadius: Float get() = - if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.bottomCornerRadius + if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius else bottomRoundness * maxRadius /** Get and update the current radii */ - @JvmDefault val updatedRadii: FloatArray get() = roundableState.radiiBuffer.also { radii -> @@ -80,7 +74,6 @@ interface Roundable { * @param sourceType the source from which the request for roundness comes. * @return Whether the roundness was changed. */ - @JvmDefault fun requestTopRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, sourceType: SourceType, @@ -125,7 +118,6 @@ interface Roundable { * @param sourceType the source from which the request for roundness comes. * @return Whether the roundness was changed. */ - @JvmDefault fun requestTopRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, sourceType: SourceType, @@ -149,7 +141,6 @@ interface Roundable { * @param sourceType the source from which the request for roundness comes. * @return Whether the roundness was changed. */ - @JvmDefault fun requestBottomRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, sourceType: SourceType, @@ -194,7 +185,6 @@ interface Roundable { * @param sourceType the source from which the request for roundness comes. * @return Whether the roundness was changed. */ - @JvmDefault fun requestBottomRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, sourceType: SourceType, @@ -219,7 +209,6 @@ interface Roundable { * @param animate true if it should animate to that value. * @return Whether the roundness was changed. */ - @JvmDefault fun requestRoundness( @FloatRange(from = 0.0, to = 1.0) top: Float, @FloatRange(from = 0.0, to = 1.0) bottom: Float, @@ -246,7 +235,6 @@ interface Roundable { * @param sourceType the source from which the request for roundness comes. * @return Whether the roundness was changed. */ - @JvmDefault fun requestRoundness( @FloatRange(from = 0.0, to = 1.0) top: Float, @FloatRange(from = 0.0, to = 1.0) bottom: Float, @@ -270,7 +258,6 @@ interface Roundable { * @param sourceType the source from which the request for roundness comes. * @param animate true if it should animate to that value. */ - @JvmDefault fun requestRoundnessReset(sourceType: SourceType, animate: Boolean) { requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType, animate = animate) } @@ -284,19 +271,16 @@ interface Roundable { * * @param sourceType the source from which the request for roundness comes. */ - @JvmDefault fun requestRoundnessReset(sourceType: SourceType) { requestRoundnessReset(sourceType = sourceType, animate = roundableState.targetView.isShown) } /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */ - @JvmDefault fun applyRoundnessAndInvalidate() { roundableState.targetView.invalidate() } /** @return true if top or bottom roundness is not zero. */ - @JvmDefault fun hasRoundedCorner(): Boolean { return topRoundness != 0f || bottomRoundness != 0f } @@ -307,7 +291,6 @@ interface Roundable { * * This method reuses the previous [radii] for performance reasons. */ - @JvmDefault fun updateRadii( topCornerRadius: Float, bottomCornerRadius: Float, @@ -335,13 +318,12 @@ constructor( internal val targetView: View, private val roundable: Roundable, maxRadius: Float, - private val featureFlags: FeatureFlags = Dependency.get(FeatureFlags::class.java) + featureFlags: FeatureFlags? = null ) { internal var maxRadius = maxRadius private set - internal val newHeadsUpAnimFlagEnabled - get() = featureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS) + internal val newHeadsUpAnim = ViewRefactorFlag(featureFlags, Flags.IMPROVED_HUN_ANIMATIONS) /** Animatable for top roundness */ private val topAnimatable = topAnimatable(roundable) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt index 1494574b26f0..93a34afc9fc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt @@ -48,6 +48,14 @@ class RowAppearanceCoordinator @Inject internal constructor( private val mAlwaysExpandNonGroupedNotification = context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications) + /** + * `true` if the first non-group expandable notification should be expanded automatically + * when possible. If `false`, then the first non-group expandable notification should not + * be expanded. + */ + private val mAutoExpandFirstNotification = + context.resources.getBoolean(R.bool.config_autoExpandFirstNotification) + override fun attach(pipeline: NotifPipeline) { pipeline.addOnBeforeRenderListListener(::onBeforeRenderList) pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry) @@ -61,8 +69,10 @@ class RowAppearanceCoordinator @Inject internal constructor( private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) { // If mAlwaysExpandNonGroupedNotification is false, then only expand the - // very first notification and if it's not a child of grouped notifications. - controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification || entry == entryToExpand) + // very first notification if it's not a child of grouped notifications and when + // mAutoExpandFirstNotification is true. + controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification || + (mAutoExpandFirstNotification && entry == entryToExpand)) // Show/hide the feedback icon controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry)) // Show the "alerted" bell icon diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 189608072ec7..9ecf50ee4f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; -import static com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -176,8 +175,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { entry.setRow(row); mNotifBindPipeline.manageRow(entry, row); mPresenter.onBindRow(row); - row.setInlineReplyAnimationFlagEnabled( - mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 908c11a1d076..36a8e9833d39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -566,7 +566,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public float getTopCornerRadius() { - if (isNewHeadsUpAnimFlagEnabled()) { + if (mImprovedHunAnimation.isEnabled()) { return super.getTopCornerRadius(); } @@ -576,7 +576,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public float getBottomCornerRadius() { - if (isNewHeadsUpAnimFlagEnabled()) { + if (mImprovedHunAnimation.isEnabled()) { return super.getBottomCornerRadius(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index b34c28163abb..42b99a1dc68c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -77,6 +77,7 @@ import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -275,7 +276,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private OnExpandClickListener mOnExpandClickListener; private View.OnClickListener mOnFeedbackClickListener; private Path mExpandingClipPath; - private boolean mIsInlineReplyAnimationFlagEnabled = false; + private final ViewRefactorFlag mInlineReplyAnimation = + new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); // Listener will be called when receiving a long click event. // Use #setLongPressPosition to optionally assign positional data with the long press. @@ -3121,10 +3123,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering(); } - public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) { - mIsInlineReplyAnimationFlagEnabled = isEnabled; - } - @Override public void setActualHeight(int height, boolean notifyListeners) { boolean changed = height != getActualHeight(); @@ -3144,7 +3142,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } int contentHeight = Math.max(getMinHeight(), height); for (NotificationContentView l : mLayouts) { - if (mIsInlineReplyAnimationFlagEnabled) { + if (mInlineReplyAnimation.isEnabled()) { l.setContentHeight(height); } else { l.setContentHeight(contentHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 7f23c1b89b51..c8f13a6302cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -28,10 +28,9 @@ import android.util.IndentingPrintWriter; import android.view.View; import android.view.ViewOutlineProvider; -import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.statusbar.notification.RoundableState; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.util.DumpUtilsKt; @@ -50,7 +49,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { private float mOutlineAlpha = -1f; private boolean mAlwaysRoundBothCorners; private Path mTmpPath = new Path(); - private final FeatureFlags mFeatureFlags; + protected final ViewRefactorFlag mImprovedHunAnimation = + new ViewRefactorFlag(Flags.IMPROVED_HUN_ANIMATIONS); /** * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when @@ -126,7 +126,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { return EMPTY_PATH; } float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius(); - if (!isNewHeadsUpAnimFlagEnabled() && (topRadius + bottomRadius > height)) { + if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) { float overShoot = topRadius + bottomRadius - height; float currentTopRoundness = getTopRoundness(); float currentBottomRoundness = getBottomRoundness(); @@ -167,7 +167,6 @@ public abstract class ExpandableOutlineView extends ExpandableView { super(context, attrs); setOutlineProvider(mProvider); initDimens(); - mFeatureFlags = Dependency.get(FeatureFlags.class); } @Override @@ -376,8 +375,4 @@ public abstract class ExpandableOutlineView extends ExpandableView { }); } - // TODO(b/290365128) replace with ViewRefactorFlag - protected boolean isNewHeadsUpAnimFlagEnabled() { - return mFeatureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 23a58d252ba6..22a87a7c9432 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl @@ -66,7 +65,7 @@ class NotificationShelfViewBinderWrapperControllerImpl @Inject constructor() : override fun setOnClickListener(listener: View.OnClickListener) = unsupported private val unsupported: Nothing - get() = NotificationShelfController.throwIllegalFlagStateError(expected = true) + get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled") } /** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */ @@ -80,8 +79,6 @@ object NotificationShelfViewBinder { ) { ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager) shelf.apply { - setRefactorFlagEnabled(true) - setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)) // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind() notificationIconAreaController.setShelfIcons(shelfIcons) repeatWhenAttached { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index b0f3f598cb91..95e74f210c5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -29,7 +29,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; @@ -57,7 +56,6 @@ public class AmbientState implements Dumpable { private final SectionProvider mSectionProvider; private final BypassController mBypassController; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - private final FeatureFlags mFeatureFlags; /** * Used to read bouncer states. */ @@ -261,13 +259,12 @@ public class AmbientState implements Dumpable { @NonNull SectionProvider sectionProvider, @NonNull BypassController bypassController, @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager, - @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator, - @NonNull FeatureFlags featureFlags) { + @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator + ) { mSectionProvider = sectionProvider; mBypassController = bypassController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; - mFeatureFlags = featureFlags; reload(context); dumpManager.registerDumpable(this); } @@ -753,10 +750,6 @@ public class AmbientState implements Dumpable { return mLargeScreenShadeInterpolator; } - public FeatureFlags getFeatureFlags() { - return mFeatureFlags; - } - @Override public void dump(PrintWriter pw, String[] args) { pw.println("mTopPadding=" + mTopPadding); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index c1ceb3ce5ab6..d71bc2fd6470 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -89,6 +89,7 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.shade.ShadeController; @@ -198,7 +199,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private Set<Integer> mDebugTextUsedYPositions; private final boolean mDebugRemoveAnimation; private final boolean mSensitiveRevealAnimEndabled; - private boolean mAnimatedInsets; + private final ViewRefactorFlag mAnimatedInsets; + private final ViewRefactorFlag mShelfRefactor; private int mContentHeight; private float mIntrinsicContentHeight; @@ -621,7 +623,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); - setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS)); + mAnimatedInsets = + new ViewRefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -660,7 +664,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mGroupMembershipManager = Dependency.get(GroupMembershipManager.class); mGroupExpansionManager = Dependency.get(GroupExpansionManager.class); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - if (mAnimatedInsets) { + if (mAnimatedInsets.isEnabled()) { setWindowInsetsAnimationCallback(mInsetsCallback); } } @@ -730,11 +734,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @VisibleForTesting - void setAnimatedInsetsEnabled(boolean enabled) { - mAnimatedInsets = enabled; - } - - @VisibleForTesting public void updateFooter() { if (mFooterView == null) { return; @@ -1773,7 +1772,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return; } mForcedScroll = v; - if (mAnimatedInsets) { + if (mAnimatedInsets.isEnabled()) { updateForcedScroll(); } else { scrollTo(v); @@ -1822,7 +1821,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (!mAnimatedInsets) { + if (!mAnimatedInsets.isEnabled()) { mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom; } mWaterfallTopInset = 0; @@ -1830,11 +1829,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (cutout != null) { mWaterfallTopInset = cutout.getWaterfallInsets().top; } - if (mAnimatedInsets && !mIsInsetAnimationRunning) { + if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) { // update bottom inset e.g. after rotation updateBottomInset(insets); } - if (!mAnimatedInsets) { + if (!mAnimatedInsets.isEnabled()) { int range = getScrollRange(); if (mOwnScrollY > range) { // HACK: We're repeatedly getting staggered insets here while the IME is @@ -2714,7 +2713,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param listener callback for notification removed */ public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) { - NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags()); + mShelfRefactor.assertDisabled(); mOnNotificationRemovedListener = listener; } @@ -2727,7 +2726,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (!mChildTransferInProgress) { onViewRemovedInternal(expandableView, this); } - if (mAmbientState.getFeatureFlags().isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + if (mShelfRefactor.isEnabled()) { mShelf.requestRoundnessResetFor(expandableView); } else { if (mOnNotificationRemovedListener != null) { @@ -4943,18 +4942,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Nullable public ExpandableView getShelf() { - if (NotificationShelfController.checkRefactorFlagEnabled(mAmbientState.getFeatureFlags())) { - return mShelf; - } else { - return null; - } + if (!mShelfRefactor.expectEnabled()) return null; + return mShelf; } public void setShelf(NotificationShelf shelf) { - if (!NotificationShelfController.checkRefactorFlagEnabled( - mAmbientState.getFeatureFlags())) { - return; - } + if (!mShelfRefactor.expectEnabled()) return; int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); @@ -4968,7 +4961,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public void setShelfController(NotificationShelfController notificationShelfController) { - NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags()); + mShelfRefactor.assertDisabled(); int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index ef7375aa690b..4668aa433533 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -65,6 +65,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.shared.model.KeyguardState; @@ -205,6 +206,7 @@ public class NotificationStackScrollLayoutController { private boolean mIsInTransitionToAod = false; private final FeatureFlags mFeatureFlags; + private final ViewRefactorFlag mShelfRefactor; private final NotificationTargetsHelper mNotificationTargetsHelper; private final SecureSettings mSecureSettings; private final NotificationDismissibilityProvider mDismissibilityProvider; @@ -718,6 +720,7 @@ public class NotificationStackScrollLayoutController { mShadeController = shadeController; mNotifIconAreaController = notifIconAreaController; mFeatureFlags = featureFlags; + mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mNotificationTargetsHelper = notificationTargetsHelper; mSecureSettings = secureSettings; mDismissibilityProvider = dismissibilityProvider; @@ -1432,7 +1435,7 @@ public class NotificationStackScrollLayoutController { } public void setShelfController(NotificationShelfController notificationShelfController) { - NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags); + mShelfRefactor.assertDisabled(); mView.setShelfController(notificationShelfController); } @@ -1645,12 +1648,12 @@ public class NotificationStackScrollLayoutController { } public void setShelf(NotificationShelf shelf) { - if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return; + if (!mShelfRefactor.expectEnabled()) return; mView.setShelf(shelf); } public int getShelfHeight() { - if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) { + if (!mShelfRefactor.expectEnabled()) { return 0; } ExpandableView shelf = mView.getShelf(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 8361d6ba1801..c3c9a61df2ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2061,6 +2061,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { void updateDisplaySize() { mDisplay.getMetrics(mDisplayMetrics); mDisplay.getSize(mCurrentDisplaySize); + mMediaManager.onDisplayUpdated(mDisplay); if (DEBUG_GESTURES) { mGestureRec.tag("display", String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index b2c39f7e289f..92c786fb569f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -306,19 +306,25 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen /** * Drawable that aligns left horizontally and center vertically (like ImageWallpaper). + * + * <p>Aligns to the center when showing on the smaller internal display of a multi display + * device. */ public static class WallpaperDrawable extends DrawableWrapper { private final ConstantState mState; private final Rect mTmpRect = new Rect(); + private boolean mIsOnSmallerInternalDisplays; - public WallpaperDrawable(Resources r, Bitmap b) { - this(r, new ConstantState(b)); + public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) { + this(r, new ConstantState(b), isOnSmallerInternalDisplays); } - private WallpaperDrawable(Resources r, ConstantState state) { + private WallpaperDrawable(Resources r, ConstantState state, + boolean isOnSmallerInternalDisplays) { super(new BitmapDrawable(r, state.mBackground)); mState = state; + mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; } @Override @@ -357,10 +363,17 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } dy = (vheight - dheight * scale) * 0.5f; + int offsetX = 0; + // Offset to show the center area of the wallpaper on a smaller display for multi + // display device + if (mIsOnSmallerInternalDisplays) { + offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2); + } + mTmpRect.set( - bounds.left, + bounds.left + offsetX, bounds.top + Math.round(dy), - bounds.left + Math.round(dwidth * scale), + bounds.left + Math.round(dwidth * scale) + offsetX, bounds.top + Math.round(dheight * scale + dy)); super.onBoundsChange(mTmpRect); @@ -371,6 +384,17 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen return mState; } + /** + * Update bounds when the hosting display or the display size has changed. + * + * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal + * displays with the smaller area. + */ + public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) { + mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; + onBoundsChange(getBounds()); + } + static class ConstantState extends Drawable.ConstantState { private final Bitmap mBackground; @@ -386,7 +410,7 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen @Override public Drawable newDrawable(@Nullable Resources res) { - return new WallpaperDrawable(res, this); + return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index e18c9d86d74b..0bf0f4b504b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -24,6 +24,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.flags.ViewRefactorFlag; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -88,7 +90,7 @@ public class NotificationIconAreaController implements private final ArrayList<Rect> mTintAreas = new ArrayList<>(); private final Context mContext; - private final FeatureFlags mFeatureFlags; + private final ViewRefactorFlag mShelfRefactor; private int mAodIconAppearTranslation; @@ -120,12 +122,13 @@ public class NotificationIconAreaController implements Optional<Bubbles> bubblesOptional, DemoModeController demoModeController, DarkIconDispatcher darkIconDispatcher, - FeatureFlags featureFlags, StatusBarWindowController statusBarWindowController, + FeatureFlags featureFlags, + StatusBarWindowController statusBarWindowController, ScreenOffAnimationController screenOffAnimationController) { mContrastColorUtil = ContrastColorUtil.getInstance(context); mContext = context; mStatusBarStateController = statusBarStateController; - mFeatureFlags = featureFlags; + mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mStatusBarStateController.addCallback(this); mMediaManager = notificationMediaManager; mDozeParameters = dozeParameters; @@ -179,12 +182,12 @@ public class NotificationIconAreaController implements } public void setupShelf(NotificationShelfController notificationShelfController) { - NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags); + mShelfRefactor.assertDisabled(); mShelfIcons = notificationShelfController.getShelfIcons(); } public void setShelfIcons(NotificationIconContainer icons) { - if (NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) { + if (mShelfRefactor.expectEnabled()) { mShelfIcons = icons; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 1d934d65df7e..79151fd987d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -548,7 +548,7 @@ public class PhoneStatusBarPolicy final int iconResId = mUserManager.getUserStatusBarIconResId(userId); // TODO(b/170249807, b/230779281): Handle non-managed-profile String String accessibilityString = getManagedProfileAccessibilityString(); - mHandler.post(() -> { + mMainExecutor.execute(() -> { final boolean showIcon; if (iconResId != Resources.ID_NULL && (!mKeyguardStateController.isShowing() || mKeyguardStateController.isOccluded())) { @@ -629,6 +629,13 @@ public class PhoneStatusBarPolicy } @Override + public void appTransitionFinished(int displayId) { + if (mDisplayId == displayId) { + updateProfileIcon(); + } + } + + @Override public void onKeyguardShowingChanged() { updateProfileIcon(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index a9135ac91e44..ad8530df0523 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -79,7 +79,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu private final HeadsUpManagerPhone mHeadsUpManager; private final AboveShelfObserver mAboveShelfObserver; private final DozeScrimController mDozeScrimController; - private final CentralSurfaces mCentralSurfaces; private final NotificationsInteractor mNotificationsInteractor; private final NotificationStackScrollLayoutController mNsslController; private final LockscreenShadeTransitionController mShadeTransitionController; @@ -107,7 +106,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, - CentralSurfaces centralSurfaces, NotificationsInteractor notificationsInteractor, LockscreenShadeTransitionController shadeTransitionController, PowerInteractor powerInteractor, @@ -128,8 +126,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu mQsController = quickSettingsController; mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; - // TODO: use KeyguardStateController#isOccluded to remove this dependency - mCentralSurfaces = centralSurfaces; mNotificationsInteractor = notificationsInteractor; mNsslController = stackScrollerController; mShadeTransitionController = shadeTransitionController; @@ -283,7 +279,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu @Override public boolean suppressAwakeHeadsUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); - if (mCentralSurfaces.isOccluded()) { + if (mKeyguardStateController.isOccluded()) { boolean devicePublic = mLockscreenUserManager .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); boolean userPublic = devicePublic diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 5d09e064604a..a501e87902a8 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -20,6 +20,11 @@ import android.content.Intent; import com.android.systemui.Dependency; +/** + * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead + * or {@code SettingsObserver} to be able to specify the handler. + */ +@Deprecated public abstract class TunerService { public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER"; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 8cfe2eac3d33..ccc0a79d2cfe 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -56,7 +56,11 @@ import javax.inject.Inject; /** + * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead + * or {@code SettingsObserver} to be able to specify the handler. + * This class will interact with SecureSettings using the main looper. */ +@Deprecated @SysUISingleton public class TunerServiceImpl extends TunerService { diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 8cf71a0b78c0..b0e8ec2f2df6 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -44,8 +44,7 @@ import com.android.systemui.recents.RecentsImplementation; import com.android.systemui.screenshot.ReferenceScreenshotModule; import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.shade.ShadeControllerEmptyImpl; +import com.android.systemui.shade.ShadeEmptyImplModule; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyboardShortcutsModule; @@ -95,6 +94,7 @@ import javax.inject.Named; PowerModule.class, QSModule.class, ReferenceScreenshotModule.class, + ShadeEmptyImplModule.class, StatusBarEventsModule.class, VolumeModule.class, KeyboardShortcutsModule.class @@ -139,9 +139,6 @@ public abstract class TvSystemUIModule { @Binds abstract DockManager bindDockManager(DockManagerImpl dockManager); - @Binds - abstract ShadeController provideShadeController(ShadeControllerEmptyImpl shadeController); - @SysUISingleton @Provides @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) diff --git a/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt index 693c2708b0f7..5582ced1bad6 100644 --- a/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt @@ -23,7 +23,6 @@ import android.os.UserHandle * changes. */ interface UserAwareController { - @JvmDefault fun changeUser(newUser: UserHandle) {} val currentUserId: Int diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 6decb88ee148..5867a40c78fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -30,6 +30,8 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -43,12 +45,14 @@ import org.junit.runner.RunWith; @RunWithLooper public class ExpandHelperTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private ExpandableNotificationRow mRow; private ExpandHelper mExpandHelper; private ExpandHelper.Callback mCallback; @Before public void setUp() throws Exception { + mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); mDependency.injectMockDependency(KeyguardUpdateMonitor.class); mDependency.injectMockDependency(NotificationMediaManager.class); allowTestableLooperAsMainThread(); @@ -56,7 +60,8 @@ public class ExpandHelperTest extends SysuiTestCase { NotificationTestHelper helper = new NotificationTestHelper( mContext, mDependency, - TestableLooper.get(this)); + TestableLooper.get(this), + mFeatureFlags); mRow = helper.createRow(); mCallback = mock(ExpandHelper.Callback.class); mExpandHelper = new ExpandHelper(context, mCallback, 10, 100); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 608778e05dad..1dc8453a90ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -87,6 +88,7 @@ import java.util.function.Consumer; @RunWithLooper public class ExpandableNotificationRowTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private NotificationTestHelper mNotificationTestHelper; @Rule public MockitoRule mockito = MockitoJUnit.rule(); @@ -96,12 +98,10 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mNotificationTestHelper = new NotificationTestHelper( mContext, mDependency, - TestableLooper.get(this)); + TestableLooper.get(this), + mFeatureFlags); mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); - - FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); - fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false); - mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags); + mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM); } @Test @@ -183,6 +183,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue() + throws Exception { + FakeFeatureFlags flags = mFeatureFlags; + flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); + testSetSensitiveOnNotifRowNotifiesOfHeightChange(); + } + + @Test public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception { // GIVEN a sensitive notification row that's currently redacted ExpandableNotificationRow row = mNotificationTestHelper.createRow(); @@ -199,10 +207,19 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { // WHEN the row is set to no longer be sensitive row.setSensitive(false, true); + boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); // VERIFY that the height change listener is invoked assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout()); assertThat(row.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(row), eq(false)); + verify(listener).onHeightChanged(eq(row), eq(expectAnimation)); + } + + @Test + public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue() + throws Exception { + FakeFeatureFlags flags = mFeatureFlags; + flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); + testSetSensitiveOnGroupRowNotifiesOfHeightChange(); } @Test @@ -222,10 +239,19 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { // WHEN the row is set to no longer be sensitive group.setSensitive(false, true); + boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); // VERIFY that the height change listener is invoked assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout()); assertThat(group.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(group), eq(false)); + verify(listener).onHeightChanged(eq(group), eq(expectAnimation)); + } + + @Test + public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue() + throws Exception { + FakeFeatureFlags flags = mFeatureFlags; + flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); + testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange(); } @Test @@ -254,7 +280,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0); assertThat(publicRow.getPrivateLayout().getMinHeight()) .isEqualTo(publicRow.getPublicLayout().getMinHeight()); - verify(listener, never()).onHeightChanged(eq(publicRow), eq(false)); + verify(listener, never()).onHeightChanged(eq(publicRow), anyBoolean()); } private void measureAndLayout(ExpandableNotificationRow row) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 1a644d3540b0..d21029d33d5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -48,12 +48,16 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; +import androidx.annotation.NonNull; + import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -90,6 +94,7 @@ import com.android.systemui.wmshell.BubblesTestActivity; import org.mockito.ArgumentCaptor; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -130,14 +135,24 @@ public class NotificationTestHelper { private final NotificationDismissibilityProvider mDismissibilityProvider; public final Runnable mFutureDismissalRunnable; private @InflationFlag int mDefaultInflationFlags; - private FeatureFlags mFeatureFlags; + private final FakeFeatureFlags mFeatureFlags; public NotificationTestHelper( Context context, TestableDependency dependency, TestableLooper testLooper) { + this(context, dependency, testLooper, new FakeFeatureFlags()); + } + + public NotificationTestHelper( + Context context, + TestableDependency dependency, + TestableLooper testLooper, + @NonNull FakeFeatureFlags featureFlags) { mContext = context; mTestLooper = testLooper; + mFeatureFlags = Objects.requireNonNull(featureFlags); + dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); dependency.injectMockDependency(NotificationMediaManager.class); dependency.injectMockDependency(NotificationShadeWindowController.class); dependency.injectMockDependency(MediaOutputDialogFactory.class); @@ -183,17 +198,12 @@ public class NotificationTestHelper { mFutureDismissalRunnable = mock(Runnable.class); when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt())) .thenReturn(mFutureDismissalRunnable); - mFeatureFlags = mock(FeatureFlags.class); } public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) { mDefaultInflationFlags = defaultInflationFlags; } - public void setFeatureFlags(FeatureFlags featureFlags) { - mFeatureFlags = featureFlags; - } - public ExpandableNotificationRowLogger getMockLogger() { return mMockLogger; } @@ -527,6 +537,10 @@ public class NotificationTestHelper { @InflationFlag int extraInflationFlags, int importance) throws Exception { + // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be + // set, but we do not want to override an existing value that is needed by a specific test. + mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS); + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( mContext.LAYOUT_INFLATER_SERVICE); mRow = (ExpandableNotificationRow) inflater.inflate( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt index 09382ec1945e..3d752880f423 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt @@ -20,7 +20,6 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager @@ -42,7 +41,6 @@ class AmbientStateTest : SysuiTestCase() { private val bypassController = StackScrollAlgorithm.BypassController { false } private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>() private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>() - private val featureFlags = mock<FeatureFlags>() private lateinit var sut: AmbientState @@ -55,8 +53,7 @@ class AmbientStateTest : SysuiTestCase() { sectionProvider, bypassController, statusBarKeyguardViewManager, - largeScreenShadeInterpolator, - featureFlags + largeScreenShadeInterpolator ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index f38881c5b521..4b145d8b0dd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -30,7 +30,6 @@ import android.view.ViewGroup; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.controls.ui.KeyguardMediaController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; @@ -65,7 +64,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Mock private SectionHeaderController mPeopleHeaderController; @Mock private SectionHeaderController mAlertingHeaderController; @Mock private SectionHeaderController mSilentHeaderController; - @Mock private FeatureFlags mFeatureFlag; private NotificationSectionsManager mSectionsManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 8d751e3b2808..1dc0ab07349b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -9,7 +9,9 @@ import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerPr import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation +import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.StatusBarIconView @@ -20,6 +22,7 @@ import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,22 +37,32 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper -class NotificationShelfTest : SysuiTestCase() { +open class NotificationShelfTest : SysuiTestCase() { + + open val useShelfRefactor: Boolean = false + open val useSensitiveReveal: Boolean = false + private val flags = FakeFeatureFlags() @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator @Mock - private lateinit var flags: FeatureFlags - @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var hostLayoutController: NotificationStackScrollLayoutController + @Mock + private lateinit var hostLayout: NotificationStackScrollLayout + @Mock + private lateinit var roundnessManager: NotificationRoundnessManager private lateinit var shelf: NotificationShelf @Before fun setUp() { MockitoAnnotations.initMocks(this) + mDependency.injectTestDependency(FeatureFlags::class.java, flags) + flags.set(Flags.NOTIFICATION_SHELF_REFACTOR, useShelfRefactor) + flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal) + flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS) val root = FrameLayout(context) shelf = LayoutInflater.from(root.context) .inflate(/* resource = */ R.layout.status_bar_notification_shelf, @@ -57,10 +70,13 @@ class NotificationShelfTest : SysuiTestCase() { /* attachToRoot = */false) as NotificationShelf whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator) - whenever(ambientState.featureFlags).thenReturn(flags) whenever(ambientState.isSmallScreen).thenReturn(true) - shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController) + if (useShelfRefactor) { + shelf.bind(ambientState, hostLayout, roundnessManager) + } else { + shelf.bind(ambientState, hostLayoutController) + } shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5) } @@ -345,7 +361,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withNullLastVisibleBackgroundChild_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -372,7 +388,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withNullFirstViewInShelf_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -399,7 +415,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withCollapsedShade_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -426,7 +442,7 @@ class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withHiddenSectionBeforeShelf_hideShelf() { // GIVEN - shelf.setSensitiveRevealAnimEnabled(true) + assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -486,3 +502,25 @@ class NotificationShelfTest : SysuiTestCase() { assertEquals(expectedAlpha, shelf.viewState.alpha) } } + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationShelfWithRefactorTest : NotificationShelfTest() { + override val useShelfRefactor: Boolean = true +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() { + override val useSensitiveReveal: Boolean = true +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationShelfWithBothFlagsTest : NotificationShelfTest() { + override val useShelfRefactor: Boolean = true + override val useSensitiveReveal: Boolean = true +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index ee8325ec02b5..07eadf7c9bb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -54,7 +54,6 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -119,6 +118,7 @@ import java.util.Optional; @RunWith(AndroidTestingRunner.class) public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private NotificationGutsManager mNotificationGutsManager; @Mock private NotificationsController mNotificationsController; @Mock private NotificationVisibilityProvider mVisibilityProvider; @@ -157,7 +157,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private StackStateLogger mStackLogger; @Mock private NotificationStackScrollLogger mLogger; @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; - private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private NotificationTargetsHelper mNotificationTargetsHelper; @Mock private SecureSettings mSecureSettings; @Mock private NotificationIconAreaController mIconAreaController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 8ad271bef2e4..72fcdec3c44c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -68,7 +68,9 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; @@ -106,6 +108,7 @@ import java.util.ArrayList; @TestableLooper.RunWithLooper public class NotificationStackScrollLayoutTest extends SysuiTestCase { + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private NotificationStackScrollLayout mStackScroller; // Normally test this private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below private AmbientState mAmbientState; @@ -129,7 +132,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - @Mock private FeatureFlags mFeatureFlags; @Before public void setUp() throws Exception { @@ -143,11 +145,25 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mNotificationSectionsManager, mBypassController, mStatusBarKeyguardViewManager, - mLargeScreenShadeInterpolator, - mFeatureFlags + mLargeScreenShadeInterpolator )); + // Register the debug flags we use + assertFalse(Flags.NSSL_DEBUG_LINES.getDefault()); + assertFalse(Flags.NSSL_DEBUG_REMOVE_ANIMATION.getDefault()); + mFeatureFlags.set(Flags.NSSL_DEBUG_LINES, false); + mFeatureFlags.set(Flags.NSSL_DEBUG_REMOVE_ANIMATION, false); + + // Register the feature flags we use + // TODO: Ideally we wouldn't need to set these unless a test actually reads them, + // and then we would test both configurations, but currently they are all read + // in the constructor. + mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM); + mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR); + // Inject dependencies before initializing the layout + mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState); mDependency.injectMockDependency(ShadeController.class); mDependency.injectTestDependency( @@ -176,13 +192,18 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper, mNotificationStackSizeCalculator); mStackScroller = spy(mStackScrollerInternal); - mStackScroller.setShelfController(notificationShelfController); + if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + mStackScroller.setShelfController(notificationShelfController); + } mStackScroller.setNotificationsController(mNotificationsController); mStackScroller.setEmptyShadeView(mEmptyShadeView); when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true); when(mStackScrollLayoutController.getNotificationRoundnessManager()) .thenReturn(mNotificationRoundnessManager); mStackScroller.setController(mStackScrollLayoutController); + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + mStackScroller.setShelf(mNotificationShelf); + } doNothing().when(mGroupExpansionManager).collapseGroups(); doNothing().when(mExpandHelper).cancelImmediately(); @@ -899,7 +920,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testWindowInsetAnimationProgress_updatesBottomInset() { int bottomImeInset = 100; - mStackScrollerInternal.setAnimatedInsetsEnabled(true); WindowInsets windowInsets = new WindowInsets.Builder() .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build(); ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index df65c09eb8a9..85a2bdd21073 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -47,6 +47,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; @@ -83,7 +84,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { private Handler mHandler; private ExpandableNotificationRow mNotificationRow; private Runnable mFalsingCheck; - private FeatureFlags mFeatureFlags; + private final FeatureFlags mFeatureFlags = new FakeFeatureFlags(); private static final int FAKE_ROW_WIDTH = 20; private static final int FAKE_ROW_HEIGHT = 20; @@ -96,7 +97,6 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { mCallback = mock(NotificationSwipeHelper.NotificationCallback.class); mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class); mNotificationRoundnessManager = mock(NotificationRoundnessManager.class); - mFeatureFlags = mock(FeatureFlags.class); mSwipeHelper = spy(new NotificationSwipeHelper( mContext.getResources(), ViewConfiguration.get(mContext), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt index 45725ced521c..e30947ce84bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt @@ -18,6 +18,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @RunWithLooper class NotificationTargetsHelperTest : SysuiTestCase() { + private val featureFlags = FakeFeatureFlags() lateinit var notificationTestHelper: NotificationTestHelper private val sectionsManager: NotificationSectionsManager = mock() private val stackScrollLayout: NotificationStackScrollLayout = mock() @@ -26,10 +27,10 @@ class NotificationTargetsHelperTest : SysuiTestCase() { fun setUp() { allowTestableLooperAsMainThread() notificationTestHelper = - NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + NotificationTestHelper(mContext, mDependency, TestableLooper.get(this), featureFlags) } - private fun notificationTargetsHelper() = NotificationTargetsHelper(FakeFeatureFlags()) + private fun notificationTargetsHelper() = NotificationTargetsHelper(featureFlags) @Test fun targetsForFirstNotificationInGroup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 4c97d20c5da8..987861d3f133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -8,7 +8,6 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.EmptyShadeView import com.android.systemui.statusbar.NotificationShelf @@ -45,7 +44,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() { private val dumpManager = mock<DumpManager>() private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>() private val notificationShelf = mock<NotificationShelf>() - private val featureFlags = mock<FeatureFlags>() private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) } @@ -56,7 +54,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() { /* bypassController */ { false }, mStatusBarKeyguardViewManager, largeScreenShadeInterpolator, - featureFlags, ) private val testableResources = mContext.getOrCreateTestableResources() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 85fbef0d7bb6..9795b9d3c169 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone import android.app.AlarmManager import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyResourcesManager import android.content.SharedPreferences import android.os.UserManager import android.telecom.TelecomManager @@ -49,6 +50,7 @@ import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.RingerModeTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.time.DateFormatUtil import com.android.systemui.util.time.FakeSystemClock @@ -67,6 +69,7 @@ import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.inOrder import org.mockito.Mockito.never @@ -83,6 +86,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { companion object { private const val ALARM_SLOT = "alarm" private const val CONNECTED_DISPLAY_SLOT = "connected_display" + private const val MANAGED_PROFILE_SLOT = "managed_profile" } @Mock private lateinit var iconController: StatusBarIconController @@ -104,6 +108,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { @Mock private lateinit var userManager: UserManager @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var devicePolicyManager: DevicePolicyManager + @Mock private lateinit var devicePolicyManagerResources: DevicePolicyResourcesManager @Mock private lateinit var recordingController: RecordingController @Mock private lateinit var telecomManager: TelecomManager @Mock private lateinit var sharedPreferences: SharedPreferences @@ -132,6 +137,12 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { com.android.internal.R.string.status_bar_alarm_clock, ALARM_SLOT ) + context.orCreateTestableResources.addOverride( + com.android.internal.R.string.status_bar_managed_profile, + MANAGED_PROFILE_SLOT + ) + whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources) + whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("") statusBarPolicy = createStatusBarPolicy() } @@ -182,6 +193,32 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { } @Test + fun testAppTransitionFinished_doesNotShowManagedProfileIcon() { + whenever(userManager.getUserStatusBarIconResId(anyInt())).thenReturn(0 /* ID_NULL */) + whenever(keyguardStateController.isShowing).thenReturn(false) + statusBarPolicy.appTransitionFinished(0) + // The above call posts to bgExecutor and then back to mainExecutor + executor.advanceClockToLast() + executor.runAllReady() + executor.advanceClockToLast() + executor.runAllReady() + verify(iconController, never()).setIconVisibility(MANAGED_PROFILE_SLOT, true) + } + + @Test + fun testAppTransitionFinished_showsManagedProfileIcon() { + whenever(userManager.getUserStatusBarIconResId(anyInt())).thenReturn(100) + whenever(keyguardStateController.isShowing).thenReturn(false) + statusBarPolicy.appTransitionFinished(0) + // The above call posts to bgExecutor and then back to mainExecutor + executor.advanceClockToLast() + executor.runAllReady() + executor.advanceClockToLast() + executor.runAllReady() + verify(iconController).setIconVisibility(MANAGED_PROFILE_SLOT, true) + } + + @Test fun connectedDisplay_connected_iconShown() = testScope.runTest { statusBarPolicy.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 9c7f6190de44..33144f233a71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -64,7 +64,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingCollectorFake; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; @@ -117,6 +117,7 @@ import java.util.Optional; public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { private static final int DISPLAY_ID = 0; + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private AssistManager mAssistManager; @@ -256,7 +257,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { notificationAnimationProvider, mock(LaunchFullScreenIntentProvider.class), mPowerInteractor, - mock(FeatureFlags.class), + mFeatureFlags, mUserTracker ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index cd8aaa2685c2..9c52788dc2eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -79,7 +79,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { private CommandQueue mCommandQueue; private FakeMetricsLogger mMetricsLogger; private final ShadeController mShadeController = mock(ShadeController.class); - private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class); private final NotificationsInteractor mNotificationsInteractor = mock(NotificationsInteractor.class); private final KeyguardStateController mKeyguardStateController = @@ -118,7 +117,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), mKeyguardStateController, - mCentralSurfaces, mNotificationsInteractor, mock(LockscreenShadeTransitionController.class), mock(PowerInteractor.class), @@ -202,7 +200,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { when(mKeyguardStateController.isShowing()).thenReturn(true); when(mKeyguardStateController.isOccluded()).thenReturn(false); - when(mCentralSurfaces.isOccluded()).thenReturn(false); assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 391c8ca4d286..7c285b8aa1a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -102,6 +102,8 @@ public class RemoteInputViewTest extends SysuiTestCase { "com.android.sysuitest.dummynotificationsender"; private static final int DUMMY_MESSAGE_APP_ID = Process.LAST_APPLICATION_UID - 1; + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); + @Mock private RemoteInputController mController; @Mock private ShortcutManager mShortcutManager; @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; @@ -453,8 +455,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private RemoteInputViewController bindController( RemoteInputView view, NotificationEntry entry) { - FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); - fakeFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true); + mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true); RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl( view, entry, @@ -462,7 +463,7 @@ public class RemoteInputViewTest extends SysuiTestCase { mController, mShortcutManager, mUiEventLoggerFake, - fakeFeatureFlags + mFeatureFlags ); viewController.bind(); return viewController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 4839eeba2124..17bb73b94ac2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -92,7 +92,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -305,6 +305,7 @@ public class BubblesTest extends SysuiTestCase { private TestableLooper mTestableLooper; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private UserHandle mUser0; @@ -423,7 +424,7 @@ public class BubblesTest extends SysuiTestCase { mCommonNotifCollection, mNotifPipeline, mSysUiState, - mock(FeatureFlags.class), + mFeatureFlags, mNotifPipelineFlags, syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback); @@ -432,7 +433,8 @@ public class BubblesTest extends SysuiTestCase { mNotificationTestHelper = new NotificationTestHelper( mContext, mDependency, - TestableLooper.get(this)); + TestableLooper.get(this), + mFeatureFlags); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt index b94f816e1ca4..36fa7e65d8ec 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt @@ -62,6 +62,32 @@ class FakeFeatureFlags : FeatureFlags { } } + /** + * Set the given flag's default value if no other value has been set. + * + * REMINDER: You should always test your code with your flag in both configurations, so + * generally you should be setting a particular value. This method should be reserved for + * situations where the flag needs to be read (e.g. in the class constructor), but its + * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use + * this method than to hard-code `false` or `true` because then at least if you're wrong, + * and the flag value *does* matter, you'll notice when the flag is flipped and tests + * start failing. + */ + fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + + /** + * Set the given flag's default value if no other value has been set. + * + * REMINDER: You should always test your code with your flag in both configurations, so + * generally you should be setting a particular value. This method should be reserved for + * situations where the flag needs to be read (e.g. in the class constructor), but its + * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use + * this method than to hard-code `false` or `true` because then at least if you're wrong, + * and the flag value *does* matter, you'll notice when the flag is flipped and tests + * start failing. + */ + fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + private fun notifyFlagChanged(flag: Flag<*>) { flagListeners[flag.id]?.let { listeners -> listeners.forEach { listener -> diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp index 2e0a9462ffbe..1f0181f2e5bd 100644 --- a/packages/SystemUI/unfold/Android.bp +++ b/packages/SystemUI/unfold/Android.bp @@ -33,7 +33,7 @@ android_library { "dagger2", "jsr330", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], java_version: "1.8", sdk_version: "current", min_sdk_version: "current", diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt index fee485d97afa..896444d4df5a 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt @@ -35,14 +35,12 @@ interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgre interface TransitionProgressListener { /** Called when transition is started */ - @JvmDefault fun onTransitionStarted() {} /** * Called whenever transition progress is updated, [progress] is a value of the animation * where 0 is fully folded, 1 is fully unfolded */ - @JvmDefault fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {} /** @@ -51,11 +49,9 @@ interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgre * For example, in [PhysicsBasedUnfoldTransitionProgressProvider] this could happen when the * animation is not tied to the hinge angle anymore and it is about to run fixed animation. */ - @JvmDefault fun onTransitionFinishing() {} /** Called when transition is completely finished */ - @JvmDefault fun onTransitionFinished() {} } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt index 0af372f9da24..bce7e8849903 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt @@ -31,9 +31,9 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { val isFinishedOpening: Boolean interface FoldUpdatesListener { - @JvmDefault fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) {} - @JvmDefault fun onFoldUpdate(@FoldUpdate update: Int) {} - @JvmDefault fun onUnfoldedScreenAvailable() {} + fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) {} + fun onFoldUpdate(@FoldUpdate update: Int) {} + fun onUnfoldedScreenAvailable() {} } @IntDef( diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index 1482d078fa8c..3f3fa3419117 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -246,6 +246,31 @@ public class FullScreenMagnificationController implements } @GuardedBy("mLock") + boolean isAtEdge() { + return isAtLeftEdge() || isAtRightEdge() || isAtTopEdge() || isAtBottomEdge(); + } + + @GuardedBy("mLock") + boolean isAtLeftEdge() { + return getOffsetX() == getMaxOffsetXLocked(); + } + + @GuardedBy("mLock") + boolean isAtRightEdge() { + return getOffsetX() == getMinOffsetXLocked(); + } + + @GuardedBy("mLock") + boolean isAtTopEdge() { + return getOffsetY() == getMaxOffsetYLocked(); + } + + @GuardedBy("mLock") + boolean isAtBottomEdge() { + return getOffsetY() == getMinOffsetYLocked(); + } + + @GuardedBy("mLock") float getCenterX() { return (mMagnificationBounds.width() / 2.0f + mMagnificationBounds.left - getOffsetX()) / getScale(); @@ -1086,6 +1111,87 @@ public class FullScreenMagnificationController implements } /** + * Returns whether the user is at one of the edges (left, right, top, bottom) + * of the magnification viewport + * + * @param displayId + * @return if user is at the edge of the view + */ + public boolean isAtEdge(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.isAtEdge(); + } + } + + /** + * Returns whether the user is at the left edge of the viewport + * + * @param displayId + * @return if user is at left edge of view + */ + public boolean isAtLeftEdge(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.isAtLeftEdge(); + } + } + + /** + * Returns whether the user is at the right edge of the viewport + * + * @param displayId + * @return if user is at right edge of view + */ + public boolean isAtRightEdge(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.isAtRightEdge(); + } + } + + /** + * Returns whether the user is at the top edge of the viewport + * + * @param displayId + * @return if user is at top edge of view + */ + public boolean isAtTopEdge(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.isAtTopEdge(); + } + } + + /** + * Returns whether the user is at the bottom edge of the viewport + * + * @param displayId + * @return if user is at bottom edge of view + */ + public boolean isAtBottomEdge(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.isAtBottomEdge(); + } + } + + /** * Returns the Y offset of the magnification viewport. If an animation * is in progress, this reflects the end state of the animation. * diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 038847e2a759..4aebbf11c7af 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -137,6 +137,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @VisibleForTesting final DetectingState mDetectingState; @VisibleForTesting final PanningScalingState mPanningScalingState; @VisibleForTesting final ViewportDraggingState mViewportDraggingState; + @VisibleForTesting final SinglePanningState mSinglePanningState; private final ScreenStateReceiver mScreenStateReceiver; private final WindowMagnificationPromptController mPromptController; @@ -146,7 +147,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; - + @VisibleForTesting boolean mIsSinglePanningEnabled; public FullScreenMagnificationGestureHandler(@UiContext Context context, FullScreenMagnificationController fullScreenMagnificationController, AccessibilityTraceManager trace, @@ -202,6 +203,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mDetectingState = new DetectingState(context); mViewportDraggingState = new ViewportDraggingState(); mPanningScalingState = new PanningScalingState(context); + mSinglePanningState = new SinglePanningState(context); + setSinglePanningEnabled(false); if (mDetectShortcutTrigger) { mScreenStateReceiver = new ScreenStateReceiver(context, this); @@ -213,6 +216,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH transitionTo(mDetectingState); } + @VisibleForTesting + void setSinglePanningEnabled(boolean isEnabled) { + mIsSinglePanningEnabled = isEnabled; + } + @Override void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { handleEventWith(mCurrentState, event, rawEvent, policyFlags); @@ -223,6 +231,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // To keep InputEventConsistencyVerifiers within GestureDetectors happy mPanningScalingState.mScrollGestureDetector.onTouchEvent(event); mPanningScalingState.mScaleGestureDetector.onTouchEvent(event); + mSinglePanningState.mScrollGestureDetector.onTouchEvent(event); try { stateHandler.onMotionEvent(event, rawEvent, policyFlags); @@ -669,7 +678,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - // Ensures that the state at the end of delegation is consistent with the last delegated // UP/DOWN event in queue: still delegating if pointer is down, detecting otherwise switch (event.getActionMasked()) { @@ -726,6 +734,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper(), this); + private PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN); + DetectingState(Context context) { mLongTapMinDelay = ViewConfiguration.getLongPressTimeout(); mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout() @@ -765,10 +775,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH cacheDelayedMotionEvent(event, rawEvent, policyFlags); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: { - mLastDetectingDownEventTime = event.getDownTime(); mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + mFirstPointerDownLocation.set(event.getX(), event.getY()); + if (!mFullScreenMagnificationController.magnificationRegionContains( mDisplayId, event.getX(), event.getY())) { @@ -800,7 +811,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH break; case ACTION_POINTER_DOWN: { if (isActivated() && event.getPointerCount() == 2) { - storeSecondPointerDownLocation(event); + storePointerDownLocation(mSecondPointerDownLocation, event); mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE, ViewConfiguration.getTapTimeout()); } else { @@ -815,7 +826,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH case ACTION_MOVE: { if (isFingerDown() && distance(mLastDown, /* move */ event) > mSwipeMinDistance) { - // Swipe detected - transition immediately // For convenience, viewport dragging takes precedence @@ -826,10 +836,15 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } else if (isActivated() && event.getPointerCount() == 2) { //Primary pointer is swiping, so transit to PanningScalingState transitToPanningScalingStateAndClear(); + } else if (mIsSinglePanningEnabled + && isActivated() + && event.getPointerCount() == 1 + && !isOverscroll(event)) { + transitToSinglePanningStateAndClear(); } else { transitionToDelegatingStateAndClear(); } - } else if (isActivated() && secondPointerDownValid() + } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation) && distanceClosestPointerToPoint( mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) { //Second pointer is swiping, so transit to PanningScalingState @@ -843,11 +858,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (!mFullScreenMagnificationController.magnificationRegionContains( mDisplayId, event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); } else if (isMultiTapTriggered(3 /* taps */)) { - onTripleTap(/* up */ event); } else if ( @@ -856,7 +869,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH //TODO long tap should never happen here && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay) || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) { - transitionToDelegatingStateAndClear(); } @@ -865,14 +877,28 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } - private void storeSecondPointerDownLocation(MotionEvent event) { + private boolean isOverscroll(MotionEvent event) { + if (!pointerDownValid(mFirstPointerDownLocation)) { + return false; + } + float dX = event.getX() - mFirstPointerDownLocation.x; + float dY = event.getY() - mFirstPointerDownLocation.y; + boolean didOverscroll = + mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) && dX > 0 + || mFullScreenMagnificationController.isAtRightEdge(mDisplayId) && dX < 0 + || mFullScreenMagnificationController.isAtTopEdge(mDisplayId) && dY > 0 + || mFullScreenMagnificationController.isAtBottomEdge(mDisplayId) && dY < 0; + return didOverscroll; + } + + private void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) { final int index = event.getActionIndex(); - mSecondPointerDownLocation.set(event.getX(index), event.getY(index)); + pointerDownLocation.set(event.getX(index), event.getY(index)); } - private boolean secondPointerDownValid() { - return !(Float.isNaN(mSecondPointerDownLocation.x) && Float.isNaN( - mSecondPointerDownLocation.y)); + private boolean pointerDownValid(PointF pointerDownLocation) { + return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN( + pointerDownLocation.y)); } private void transitToPanningScalingStateAndClear() { @@ -880,6 +906,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH clear(); } + private void transitToSinglePanningStateAndClear() { + transitionTo(mSinglePanningState); + clear(); + } + public boolean isMultiTapTriggered(int numTaps) { // Shortcut acts as the 2 initial taps @@ -947,6 +978,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH setShortcutTriggered(false); removePendingDelayedMessages(); clearDelayedMotionEvents(); + mFirstPointerDownLocation.set(Float.NaN, Float.NaN); mSecondPointerDownLocation.set(Float.NaN, Float.NaN); } @@ -1165,12 +1197,14 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + ", mDelegatingState=" + mDelegatingState + ", mMagnifiedInteractionState=" + mPanningScalingState + ", mViewportDraggingState=" + mViewportDraggingState + + ", mSinglePanningState=" + mSinglePanningState + ", mDetectTripleTap=" + mDetectTripleTap + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger + ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) + ", mMagnificationController=" + mFullScreenMagnificationController + ", mDisplayId=" + mDisplayId + + ", mIsSinglePanningEnabled=" + mIsSinglePanningEnabled + '}'; } @@ -1285,8 +1319,67 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * Indicates an error with a gesture handler or state. */ private static class GestureException extends Exception { + GestureException(String message) { super(message); } } + + final class SinglePanningState extends SimpleOnGestureListener implements State { + private final GestureDetector mScrollGestureDetector; + private MotionEventInfo mEvent; + + SinglePanningState(Context context) { + mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain()); + } + + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + int action = event.getActionMasked(); + switch (action) { + case ACTION_UP: + case ACTION_CANCEL: + clear(); + transitionTo(mDetectingState); + break; + } + } + + @Override + public boolean onScroll( + MotionEvent first, MotionEvent second, float distanceX, float distanceY) { + if (mCurrentState != mSinglePanningState) { + return true; + } + mFullScreenMagnificationController.offsetMagnifiedRegion( + mDisplayId, + distanceX, + distanceY, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + if (DEBUG_PANNING_SCALING) { + Slog.i( + mLogTag, + "SinglePanningState Panned content by scrollX: " + + distanceX + + " scrollY: " + + distanceY + + " isAtEdge: " + + mFullScreenMagnificationController.isAtEdge(mDisplayId)); + } + // TODO: b/280812104 Dispatch events before Delegation + if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) { + clear(); + transitionTo(mDelegatingState); + } + return /* event consumed: */ true; + } + + @Override + public String toString() { + return "SinglePanningState{" + + "isEdgeOfView=" + + mFullScreenMagnificationController.isAtEdge(mDisplayId) + + "}"; + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c9769b3f9932..26bcb9805baa 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5083,6 +5083,10 @@ public class ActivityManagerService extends IActivityManager.Stub // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); + + // Start PSI monitoring in LMKD if it was skipped earlier. + ProcessList.startPsiMonitoringAfterBoot(); + mUserController.onBootComplete( new IIntentReceiver.Stub() { @Override diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index acd9771c7750..e484a6cf251b 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -357,6 +357,7 @@ public final class ProcessList { static final byte LMK_UPDATE_PROPS = 7; static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event static final byte LMK_STATE_CHANGED = 9; // Msg to subscribed clients on state changed + static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier // Low Memory Killer Daemon command codes. // These must be kept in sync with async_event_type definitions in lmkd.h @@ -1568,6 +1569,15 @@ public final class ProcessList { return true; } + /** + * {@hide} + */ + public static void startPsiMonitoringAfterBoot() { + ByteBuffer buf = ByteBuffer.allocate(4); + buf.putInt(LMK_START_MONITORING); + writeLmkd(buf, null); + } + private static boolean writeLmkd(ByteBuffer buf, ByteBuffer repl) { if (!sLmkdConnection.isConnected()) { // try to connect immediately and then keep retrying diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2db724f51918..8e1ad6529bc0 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2491,16 +2491,6 @@ public class NotificationManagerService extends SystemService { getContext().registerReceiver(mReviewNotificationPermissionsReceiver, ReviewNotificationPermissionsReceiver.getFilter(), Context.RECEIVER_NOT_EXPORTED); - - mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, - new AppOpsManager.OnOpChangedInternalListener() { - @Override - public void onOpChanged(@NonNull String op, @NonNull String packageName, - int userId) { - mHandler.post( - () -> handleNotificationPermissionChange(packageName, userId)); - } - }); } /** @@ -3560,16 +3550,20 @@ public class NotificationManagerService extends SystemService { } mPermissionHelper.setNotificationPermission( pkg, UserHandle.getUserId(uid), enabled, true); + sendAppBlockStateChangedBroadcast(pkg, uid, !enabled); mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES) .setType(MetricsEvent.TYPE_ACTION) .setPackageName(pkg) .setSubtype(enabled ? 1 : 0)); mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled); + // Now, cancel any outstanding notifications that are part of a just-disabled app + if (!enabled) { + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, + UserHandle.getUserId(uid), REASON_PACKAGE_BANNED); + } - // Outstanding notifications from this package will be cancelled, and the package will - // be sent the ACTION_APP_BLOCK_STATE_CHANGED broadcast, as soon as we get the - // callback from AppOpsManager. + handleSavePolicyFile(); } /** @@ -5889,21 +5883,6 @@ public class NotificationManagerService extends SystemService { } }; - private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) { - int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId); - if (uid == INVALID_UID) { - Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId)); - return; - } - boolean hasPermission = mPermissionHelper.hasPermission(uid); - sendAppBlockStateChangedBroadcast(pkg, uid, !hasPermission); - if (!hasPermission) { - cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null, - /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId, - REASON_PACKAGE_BANNED); - } - } - protected void checkNotificationListenerAccess() { if (!isCallerSystemOrPhone()) { getContext().enforceCallingPermission( diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 36a0b0c0d8e9..1f5bd3e0cc60 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -75,6 +75,7 @@ import android.util.StatsEvent; import android.util.proto.ProtoOutputStream; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.logging.MetricsLogger; @@ -108,30 +109,34 @@ public class ZenModeHelper { static final int RULE_LIMIT_PER_PACKAGE = 100; // pkg|userId => uid - protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>(); + @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>(); private final Context mContext; private final H mHandler; private final SettingsObserver mSettingsObserver; private final AppOpsManager mAppOps; - @VisibleForTesting protected final NotificationManager mNotificationManager; + private final NotificationManager mNotificationManager; private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory; - @VisibleForTesting protected ZenModeConfig mDefaultConfig; + private ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ZenModeFiltering mFiltering; - protected final RingerModeDelegate mRingerModeDelegate = new + private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate(); @VisibleForTesting protected final ZenModeConditions mConditions; - Object mConfigsLock = new Object(); + private final Object mConfigsArrayLock = new Object(); + @GuardedBy("mConfigsArrayLock") @VisibleForTesting final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>(); private final Metrics mMetrics = new Metrics(); private final ConditionProviders.Config mServiceConfig; - private SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; - @VisibleForTesting protected ZenModeEventLogger mZenModeEventLogger; + private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; + private final ZenModeEventLogger mZenModeEventLogger; @VisibleForTesting protected int mZenMode; @VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy; private int mUser = UserHandle.USER_SYSTEM; + + private final Object mConfigLock = new Object(); + @GuardedBy("mConfigLock") @VisibleForTesting protected ZenModeConfig mConfig; @VisibleForTesting protected AudioManagerInternal mAudioManager; protected PackageManager mPm; @@ -159,7 +164,7 @@ public class ZenModeHelper { mDefaultConfig = readDefaultConfig(mContext.getResources()); updateDefaultAutomaticRuleNames(); mConfig = mDefaultConfig.copy(); - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.put(UserHandle.USER_SYSTEM, mConfig); } mConsolidatedPolicy = mConfig.toNotificationPolicy(); @@ -186,7 +191,7 @@ public class ZenModeHelper { public boolean matchesCallFilter(UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity, int callingUid) { - synchronized (mConfig) { + synchronized (mConfigLock) { return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy, userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity, callingUid); @@ -206,7 +211,7 @@ public class ZenModeHelper { } public boolean shouldIntercept(NotificationRecord record) { - synchronized (mConfig) { + synchronized (mConfigLock) { return mFiltering.shouldIntercept(mZenMode, mConsolidatedPolicy, record); } } @@ -221,7 +226,7 @@ public class ZenModeHelper { public void initZenMode() { if (DEBUG) Log.d(TAG, "initZenMode"); - synchronized (mConfig) { + synchronized (mConfigLock) { // "update" config to itself, which will have no effect in the case where a config // was read in via XML, but will initialize zen mode if nothing was read in and the // config remains the default. @@ -250,7 +255,7 @@ public class ZenModeHelper { public void onUserRemoved(int user) { if (user < UserHandle.USER_SYSTEM) return; if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user); - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.remove(user); } } @@ -268,7 +273,7 @@ public class ZenModeHelper { mUser = user; if (DEBUG) Log.d(TAG, reason + " u=" + user); ZenModeConfig config = null; - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { if (mConfigs.get(user) != null) { config = mConfigs.get(user).copy(); } @@ -278,7 +283,7 @@ public class ZenModeHelper { config = mDefaultConfig.copy(); config.user = user; } - synchronized (mConfig) { + synchronized (mConfigLock) { setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); } cleanUpZenRules(); @@ -314,7 +319,7 @@ public class ZenModeHelper { public List<ZenRule> getZenRules() { List<ZenRule> rules = new ArrayList<>(); - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return rules; for (ZenRule rule : mConfig.automaticRules.values()) { if (canManageAutomaticZenRule(rule)) { @@ -327,7 +332,7 @@ public class ZenModeHelper { public AutomaticZenRule getAutomaticZenRule(String id) { ZenRule rule; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return null; rule = mConfig.automaticRules.get(id); } @@ -364,7 +369,7 @@ public class ZenModeHelper { } ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) { throw new AndroidRuntimeException("Could not create rule"); } @@ -387,7 +392,7 @@ public class ZenModeHelper { public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule, String reason, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return false; if (DEBUG) { Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule @@ -419,7 +424,7 @@ public class ZenModeHelper { public boolean removeAutomaticZenRule(String id, String reason, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return false; newConfig = mConfig.copy(); ZenRule ruleToRemove = newConfig.automaticRules.get(id); @@ -450,7 +455,7 @@ public class ZenModeHelper { public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return false; newConfig = mConfig.copy(); for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) { @@ -467,7 +472,7 @@ public class ZenModeHelper { public void setAutomaticZenRuleState(String id, Condition condition, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return; newConfig = mConfig.copy(); @@ -481,7 +486,7 @@ public class ZenModeHelper { public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return; newConfig = mConfig.copy(); @@ -491,6 +496,7 @@ public class ZenModeHelper { } } + @GuardedBy("mConfigLock") private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules, Condition condition, int callingUid, boolean fromSystemOrSystemUi) { if (rules == null || rules.isEmpty()) return; @@ -538,7 +544,7 @@ public class ZenModeHelper { return 0; } int count = 0; - synchronized (mConfig) { + synchronized (mConfigLock) { for (ZenRule rule : mConfig.automaticRules.values()) { if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) { count++; @@ -555,7 +561,7 @@ public class ZenModeHelper { return 0; } int count = 0; - synchronized (mConfig) { + synchronized (mConfigLock) { for (ZenRule rule : mConfig.automaticRules.values()) { if (pkg.equals(rule.getPkg())) { count++; @@ -588,19 +594,23 @@ public class ZenModeHelper { protected void updateDefaultZenRules(int callingUid, boolean fromSystemOrSystemUi) { updateDefaultAutomaticRuleNames(); - for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) { - ZenRule currRule = mConfig.automaticRules.get(defaultRule.id); - // if default rule wasn't user-modified nor enabled, use localized name - // instead of previous system name - if (currRule != null && !currRule.modified && !currRule.enabled - && !defaultRule.name.equals(currRule.name)) { - if (canManageAutomaticZenRule(currRule)) { - if (DEBUG) Slog.d(TAG, "Locale change - updating default zen rule name " - + "from " + currRule.name + " to " + defaultRule.name); - // update default rule (if locale changed, name of rule will change) - currRule.name = defaultRule.name; - updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule), - "locale changed", callingUid, fromSystemOrSystemUi); + synchronized (mConfigLock) { + for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) { + ZenRule currRule = mConfig.automaticRules.get(defaultRule.id); + // if default rule wasn't user-modified nor enabled, use localized name + // instead of previous system name + if (currRule != null && !currRule.modified && !currRule.enabled + && !defaultRule.name.equals(currRule.name)) { + if (canManageAutomaticZenRule(currRule)) { + if (DEBUG) { + Slog.d(TAG, "Locale change - updating default zen rule name " + + "from " + currRule.name + " to " + defaultRule.name); + } + // update default rule (if locale changed, name of rule will change) + currRule.name = defaultRule.name; + updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule), + "locale changed", callingUid, fromSystemOrSystemUi); + } } } } @@ -686,7 +696,7 @@ public class ZenModeHelper { private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller, boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig == null) return; if (!Global.isValidZenMode(zenMode)) return; if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode) @@ -715,7 +725,7 @@ public class ZenModeHelper { void dump(ProtoOutputStream proto) { proto.write(ZenModeProto.ZEN_MODE, mZenMode); - synchronized (mConfig) { + synchronized (mConfigLock) { if (mConfig.manualRule != null) { mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } @@ -737,14 +747,14 @@ public class ZenModeHelper { pw.println(Global.zenModeToString(mZenMode)); pw.print(prefix); pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString()); - synchronized(mConfigsLock) { + synchronized (mConfigsArrayLock) { final int N = mConfigs.size(); for (int i = 0; i < N; i++) { dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i)); } } pw.print(prefix); pw.print("mUser="); pw.println(mUser); - synchronized (mConfig) { + synchronized (mConfigLock) { dump(pw, prefix, "mConfig", mConfig); } @@ -833,7 +843,7 @@ public class ZenModeHelper { Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId); } if (DEBUG) Log.d(TAG, reason); - synchronized (mConfig) { + synchronized (mConfigLock) { setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); } } @@ -841,7 +851,7 @@ public class ZenModeHelper { public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId) throws IOException { - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { final int n = mConfigs.size(); for (int i = 0; i < n; i++) { if (forBackup && mConfigs.keyAt(i) != userId) { @@ -856,7 +866,9 @@ public class ZenModeHelper { * @return user-specified default notification policy for priority only do not disturb */ public Policy getNotificationPolicy() { - return getNotificationPolicy(mConfig); + synchronized (mConfigLock) { + return getNotificationPolicy(mConfig); + } } private static Policy getNotificationPolicy(ZenModeConfig config) { @@ -867,8 +879,8 @@ public class ZenModeHelper { * Sets the global notification policy used for priority only do not disturb */ public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) { - if (policy == null || mConfig == null) return; - synchronized (mConfig) { + synchronized (mConfigLock) { + if (policy == null || mConfig == null) return; final ZenModeConfig newConfig = mConfig.copy(); newConfig.applyNotificationPolicy(policy); setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid, @@ -881,7 +893,7 @@ public class ZenModeHelper { */ private void cleanUpZenRules() { long currentTime = System.currentTimeMillis(); - synchronized (mConfig) { + synchronized (mConfigLock) { final ZenModeConfig newConfig = mConfig.copy(); if (newConfig.automaticRules != null) { for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) { @@ -906,7 +918,7 @@ public class ZenModeHelper { * @return a copy of the zen mode configuration */ public ZenModeConfig getConfig() { - synchronized (mConfig) { + synchronized (mConfigLock) { return mConfig.copy(); } } @@ -918,7 +930,8 @@ public class ZenModeHelper { return mConsolidatedPolicy.copy(); } - public boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, + @GuardedBy("mConfigLock") + private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, String reason, int callingUid, boolean fromSystemOrSystemUi) { return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/, callingUid, fromSystemOrSystemUi); @@ -926,11 +939,12 @@ public class ZenModeHelper { public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason, int callingUid, boolean fromSystemOrSystemUi) { - synchronized (mConfig) { + synchronized (mConfigLock) { setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi); } } + @GuardedBy("mConfigLock") private boolean setConfigLocked(ZenModeConfig config, String reason, ComponentName triggeringComponent, boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { @@ -942,7 +956,7 @@ public class ZenModeHelper { } if (config.user != mUser) { // simply store away for background users - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.put(config.user, config); } if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user); @@ -951,7 +965,7 @@ public class ZenModeHelper { // handle CPS backed conditions - danger! may modify config mConditions.evaluateConfig(config, null, false /*processSubscriptions*/); - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { mConfigs.put(config.user, config); } if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable()); @@ -979,6 +993,7 @@ public class ZenModeHelper { * Carries out a config update (if needed) and (re-)evaluates the zen mode value afterwards. * If logging is enabled, will also request logging of the outcome of this change if needed. */ + @GuardedBy("mConfigLock") private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason, boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { final boolean logZenModeEvents = mFlagResolver.isEnabled( @@ -993,7 +1008,7 @@ public class ZenModeHelper { } final String val = Integer.toString(config.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - evaluateZenMode(reason, setRingerMode); + evaluateZenModeLocked(reason, setRingerMode); // After all changes have occurred, log if requested if (logZenModeEvents) { ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo( @@ -1025,7 +1040,8 @@ public class ZenModeHelper { } @VisibleForTesting - protected void evaluateZenMode(String reason, boolean setRingerMode) { + @GuardedBy("mConfigLock") + protected void evaluateZenModeLocked(String reason, boolean setRingerMode) { if (DEBUG) Log.d(TAG, "evaluateZenMode"); if (mConfig == null) return; final int policyHashBefore = mConsolidatedPolicy == null ? 0 @@ -1056,8 +1072,8 @@ public class ZenModeHelper { } private int computeZenMode() { - if (mConfig == null) return Global.ZEN_MODE_OFF; - synchronized (mConfig) { + synchronized (mConfigLock) { + if (mConfig == null) return Global.ZEN_MODE_OFF; if (mConfig.manualRule != null) return mConfig.manualRule.zenMode; int zen = Global.ZEN_MODE_OFF; for (ZenRule automaticRule : mConfig.automaticRules.values()) { @@ -1094,8 +1110,8 @@ public class ZenModeHelper { } private void updateConsolidatedPolicy(String reason) { - if (mConfig == null) return; - synchronized (mConfig) { + synchronized (mConfigLock) { + if (mConfig == null) return; ZenPolicy policy = new ZenPolicy(); if (mConfig.manualRule != null) { applyCustomPolicy(policy, mConfig.manualRule); @@ -1293,7 +1309,7 @@ public class ZenModeHelper { * Generate pulled atoms about do not disturb configurations. */ public void pullRules(List<StatsEvent> events) { - synchronized (mConfigsLock) { + synchronized (mConfigsArrayLock) { final int numConfigs = mConfigs.size(); for (int i = 0; i < numConfigs; i++) { final int user = mConfigs.keyAt(i); @@ -1319,6 +1335,7 @@ public class ZenModeHelper { } } + @GuardedBy("mConfigsArrayLock") private void ruleToProtoLocked(int user, ZenRule rule, boolean isManualRule, List<StatsEvent> events) { // Make the ID safe. @@ -1389,7 +1406,7 @@ public class ZenModeHelper { if (mZenMode == Global.ZEN_MODE_OFF || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - && !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig))) { + && !areAllPriorityOnlyRingerSoundsMuted())) { // in priority only with ringer not muted, save ringer mode changes // in dnd off, save ringer mode changes setPreviousRingerModeSetting(ringerModeNew); @@ -1410,8 +1427,7 @@ public class ZenModeHelper { && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS || mZenMode == Global.ZEN_MODE_ALARMS || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted( - mConfig)))) { + && areAllPriorityOnlyRingerSoundsMuted()))) { newZen = Global.ZEN_MODE_OFF; } else if (mZenMode != Global.ZEN_MODE_OFF) { ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT; @@ -1430,6 +1446,12 @@ public class ZenModeHelper { return ringerModeExternalOut; } + private boolean areAllPriorityOnlyRingerSoundsMuted() { + synchronized (mConfigLock) { + return ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig); + } + } + @Override public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, int ringerModeInternal, VolumePolicy policy) { @@ -1633,7 +1655,7 @@ public class ZenModeHelper { private void emitRules() { final long now = SystemClock.elapsedRealtime(); final long since = (now - mRuleCountLogTime); - synchronized (mConfig) { + synchronized (mConfigLock) { int numZenRules = mConfig.automaticRules.size(); if (mNumZenRules != numZenRules || since > MINIMUM_LOG_PERIOD_MS) { @@ -1651,7 +1673,7 @@ public class ZenModeHelper { private void emitDndType() { final long now = SystemClock.elapsedRealtime(); final long since = (now - mTypeLogTimeMs); - synchronized (mConfig) { + synchronized (mConfigLock) { boolean dndOn = mZenMode != Global.ZEN_MODE_OFF; int zenType = !dndOn ? DND_OFF : (mConfig.manualRule != null) ? DND_ON_MANUAL : DND_ON_AUTOMATIC; diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/ArchiveManager.java index 99479f088577..54352060cd38 100644 --- a/services/core/java/com/android/server/pm/ArchiveManager.java +++ b/services/core/java/com/android/server/pm/ArchiveManager.java @@ -77,9 +77,8 @@ final class ArchiveManager { snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "archiveApp"); verifyCaller(callerPackageName, callingPackageName); - PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user); - verifyInstallOwnership(packageName, callingPackageName, ps.getInstallSource()); + verifyInstaller(packageName, ps.getInstallSource()); List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList( ps.getPackageName(), @@ -125,7 +124,7 @@ final class ArchiveManager { Path.of("/TODO"), null); activityInfos.add(activityInfo); } - // TODO(b/278553670) Adapt installer check verifyInstallOwnership and check for null there + InstallSource installSource = ps.getInstallSource(); String installerPackageName = installSource.mUpdateOwnerPackageName != null ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName; @@ -159,19 +158,13 @@ final class ArchiveManager { } } - private static void verifyInstallOwnership(String packageName, String callingPackageName, - InstallSource installSource) { - if (!TextUtils.equals(installSource.mInstallerPackageName, - callingPackageName)) { + private static void verifyInstaller(String packageName, InstallSource installSource) { + // TODO(b/291060290) Verify installer supports unarchiving + if (installSource.mUpdateOwnerPackageName == null + && installSource.mInstallerPackageName == null) { throw new SecurityException( - TextUtils.formatSimple("Caller is not the installer of record for %s.", + TextUtils.formatSimple("No installer found to archive app %s.", packageName)); } - String updateOwnerPackageName = installSource.mUpdateOwnerPackageName; - if (updateOwnerPackageName != null - && !TextUtils.equals(updateOwnerPackageName, callingPackageName)) { - throw new SecurityException( - TextUtils.formatSimple("Caller is not the update owner for %s.", packageName)); - } } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index ead26cc0cf05..134b041cb242 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -4311,7 +4311,8 @@ final class InstallPackageHelper { // - It's an APEX or overlay package since stopped state does not affect them. // - It is enumerated with a <initial-package-state> tag having the stopped attribute // set to false - // - It doesn't have a launcher entry which means the user doesn't have a way to unstop it + // - It doesn't have an enabled and exported launcher activity, which means the user + // wouldn't have a way to un-stop it final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0; if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition @@ -4337,7 +4338,11 @@ final class InstallPackageHelper { categories.add(Intent.CATEGORY_LAUNCHER); final List<ParsedActivity> activities = parsedPackage.getActivities(); for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) { - final List<ParsedIntentInfo> intents = activities.get(indexActivity).getIntents(); + final ParsedActivity activity = activities.get(indexActivity); + if (!activity.isEnabled() || !activity.isExported()) { + continue; + } + final List<ParsedIntentInfo> intents = activity.getIntents(); for (int indexIntent = 0; indexIntent < intents.size(); indexIntent++) { final IntentFilter intentFilter = intents.get(indexIntent).getIntentFilter(); if (intentFilter != null && intentFilter.matchCategories(categories) == null) { diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 3c846da9757f..5e23765df346 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -18,7 +18,12 @@ "name": "CtsCompilationTestCases" }, { - "name": "CtsAppEnumerationTestCases" + "name": "CtsAppEnumerationTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.LargeTest" + } + ] }, { "name": "CtsMatchFlagTestCases" @@ -129,6 +134,9 @@ }, { "name": "PackageManagerServiceHostTests" + }, + { + "name": "CtsAppEnumerationTestCases" } ], "imports": [ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7bbe8781e434..5e01c4939d24 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -208,7 +208,6 @@ import com.android.internal.policy.LogDecelerateInterpolator; import com.android.internal.policy.PhoneWindow; import com.android.internal.policy.TransitionAnimation; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.LockPatternUtils; import com.android.server.AccessibilityManagerInternal; @@ -228,6 +227,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback; import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.vibrator.HapticFeedbackVibrationProvider; import com.android.server.vr.VrManagerInternal; import com.android.server.wallpaper.WallpaperManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -371,12 +371,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final String TALKBACK_LABEL = "TalkBack"; private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800; - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); - private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION); - private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); /** * Keyguard stuff @@ -450,6 +444,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { PackageManager mPackageManager; SideFpsEventHandler mSideFpsEventHandler; LockPatternUtils mLockPatternUtils; + private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider; private boolean mHasFeatureAuto; private boolean mHasFeatureWatch; private boolean mHasFeatureLeanback; @@ -458,9 +453,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Assigned on main thread, accessed on UI thread volatile VrManagerInternal mVrManagerInternal; - // Vibrator pattern for haptic feedback during boot when safe mode is enabled. - long[] mSafeModeEnabledVibePattern; - /** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */ boolean mEnableShiftMenuBugReports = false; @@ -558,7 +550,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mPowerVolUpBehavior; boolean mStylusButtonsEnabled = true; boolean mHasSoftInput = false; - boolean mHapticTextHandleEnabled; boolean mUseTvRouting; boolean mAllowStartActivityForLongPressOnPowerDuringSetup; MetricsLogger mLogger; @@ -2251,9 +2242,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean( com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup); - mHapticTextHandleEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enableHapticTextHandle); - mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION; mHandleVolumeKeysInWM = mContext.getResources().getBoolean( @@ -2294,8 +2282,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mContext.registerReceiver(mMultiuserReceiver, filter); mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); - mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(), - com.android.internal.R.array.config_safeModeEnabledVibePattern); + mHapticFeedbackVibrationProvider = + new HapticFeedbackVibrationProvider(mContext.getResources(), mVibrator); mGlobalKeyManager = new GlobalKeyManager(mContext); @@ -5499,10 +5487,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - static long[] getLongIntArray(Resources r, int resid) { - return ArrayUtils.convertToLongArray(r.getIntArray(resid)); - } - private void bindKeyguard() { synchronized (mLock) { if (mKeyguardBound) { @@ -5989,138 +5973,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mVibrator.hasVibrator()) { return false; } - VibrationEffect effect = getVibrationEffect(effectId); + VibrationEffect effect = + mHapticFeedbackVibrationProvider.getVibrationForHapticFeedback(effectId); if (effect == null) { return false; } - VibrationAttributes attrs = getVibrationAttributes(effectId); - if (always) { - attrs = new VibrationAttributes.Builder(attrs) - .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) - .build(); - } + VibrationAttributes attrs = + mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ always); mVibrator.vibrate(uid, packageName, effect, reason, attrs); return true; } - private VibrationEffect getVibrationEffect(int effectId) { - long[] pattern; - switch (effectId) { - case HapticFeedbackConstants.CONTEXT_CLICK: - case HapticFeedbackConstants.GESTURE_END: - case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE: - case HapticFeedbackConstants.SCROLL_TICK: - case HapticFeedbackConstants.SEGMENT_TICK: - return VibrationEffect.get(VibrationEffect.EFFECT_TICK); - - case HapticFeedbackConstants.TEXT_HANDLE_MOVE: - if (!mHapticTextHandleEnabled) { - return null; - } - // fallthrough - case HapticFeedbackConstants.CLOCK_TICK: - case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK: - return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK); - - case HapticFeedbackConstants.KEYBOARD_RELEASE: - case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE: - case HapticFeedbackConstants.ENTRY_BUMP: - case HapticFeedbackConstants.DRAG_CROSSING: - return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false); - - case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS - case HapticFeedbackConstants.VIRTUAL_KEY: - case HapticFeedbackConstants.EDGE_RELEASE: - case HapticFeedbackConstants.CALENDAR_DATE: - case HapticFeedbackConstants.CONFIRM: - case HapticFeedbackConstants.GESTURE_START: - case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: - case HapticFeedbackConstants.SCROLL_LIMIT: - return VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - - case HapticFeedbackConstants.LONG_PRESS: - case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: - case HapticFeedbackConstants.DRAG_START: - case HapticFeedbackConstants.EDGE_SQUEEZE: - return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); - - case HapticFeedbackConstants.REJECT: - return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); - - case HapticFeedbackConstants.SAFE_MODE_ENABLED: - pattern = mSafeModeEnabledVibePattern; - break; - - case HapticFeedbackConstants.ASSISTANT_BUTTON: - if (mVibrator.areAllPrimitivesSupported( - VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, - VibrationEffect.Composition.PRIMITIVE_TICK)) { - // quiet ramp, short pause, then sharp tick - return VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50) - .compose(); - } - // fallback for devices without composition support - return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); - - case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE: - return getScaledPrimitiveOrElseEffect( - VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f, - VibrationEffect.EFFECT_TEXTURE_TICK); - - case HapticFeedbackConstants.TOGGLE_ON: - return getScaledPrimitiveOrElseEffect( - VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, - VibrationEffect.EFFECT_TICK); - - case HapticFeedbackConstants.TOGGLE_OFF: - return getScaledPrimitiveOrElseEffect( - VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 0.2f, - VibrationEffect.EFFECT_TEXTURE_TICK); - - case HapticFeedbackConstants.NO_HAPTICS: - default: - return null; - } - if (pattern.length == 0) { - // No vibration - return null; - } else if (pattern.length == 1) { - // One-shot vibration - return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE); - } else { - // Pattern vibration - return VibrationEffect.createWaveform(pattern, -1); - } - } - - private VibrationEffect getScaledPrimitiveOrElseEffect(int primitiveId, float scale, - int elseEffectId) { - if (mVibrator.areAllPrimitivesSupported(primitiveId)) { - return VibrationEffect.startComposition() - .addPrimitive(primitiveId, scale) - .compose(); - } else { - return VibrationEffect.get(elseEffectId); - } - } - - private VibrationAttributes getVibrationAttributes(int effectId) { - switch (effectId) { - case HapticFeedbackConstants.EDGE_SQUEEZE: - case HapticFeedbackConstants.EDGE_RELEASE: - return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES; - case HapticFeedbackConstants.ASSISTANT_BUTTON: - case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: - case HapticFeedbackConstants.SCROLL_TICK: - case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: - case HapticFeedbackConstants.SCROLL_LIMIT: - return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; - default: - return TOUCH_VIBRATION_ATTRIBUTES; - } - } @Override public void keepScreenOnStartedLw() { @@ -6258,8 +6122,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print("mAllowStartActivityForLongPressOnPowerDuringSetup="); pw.println(mAllowStartActivityForLongPressOnPowerDuringSetup); pw.print(prefix); - pw.print("mHasSoftInput="); pw.print(mHasSoftInput); - pw.print(" mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled); + pw.print("mHasSoftInput="); pw.println(mHasSoftInput); pw.print(prefix); pw.print("mDismissImeOnBackKeyPressed="); pw.print(mDismissImeOnBackKeyPressed); pw.print(" mIncallPowerBehavior="); @@ -6284,6 +6147,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout); pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive); + mHapticFeedbackVibrationProvider.dump(prefix, pw); mGlobalKeyManager.dump(prefix, pw); mKeyCombinationManager.dump(prefix, pw); mSingleKeyGestureDetector.dump(prefix, pw); diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java new file mode 100644 index 000000000000..308ce4f9cb68 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -0,0 +1,194 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.annotation.Nullable; +import android.content.res.Resources; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.view.HapticFeedbackConstants; + +import java.io.PrintWriter; + +/** + * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback. + * + * @hide + */ +public final class HapticFeedbackVibrationProvider { + private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); + private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + + private final Vibrator mVibrator; + private final boolean mHapticTextHandleEnabled; + // Vibrator effect for haptic feedback during boot when safe mode is enabled. + private final VibrationEffect mSafeModeEnabledVibrationEffect; + + /** @hide */ + public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) { + mVibrator = vibrator; + mHapticTextHandleEnabled = res.getBoolean( + com.android.internal.R.bool.config_enableHapticTextHandle); + mSafeModeEnabledVibrationEffect = + VibrationSettings.createEffectFromResource( + res, com.android.internal.R.array.config_safeModeEnabledVibePattern); + } + + /** + * Provides the {@link VibrationEffect} for a given haptic feedback effect ID (provided in + * {@link HapticFeedbackConstants}). + * + * @param effectId the haptic feedback effect ID whose respective vibration we want to get. + * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if + * the provided effect ID is not supported. + */ + @Nullable public VibrationEffect getVibrationForHapticFeedback(int effectId) { + switch (effectId) { + case HapticFeedbackConstants.CONTEXT_CLICK: + case HapticFeedbackConstants.GESTURE_END: + case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE: + case HapticFeedbackConstants.SCROLL_TICK: + case HapticFeedbackConstants.SEGMENT_TICK: + return VibrationEffect.get(VibrationEffect.EFFECT_TICK); + + case HapticFeedbackConstants.TEXT_HANDLE_MOVE: + if (!mHapticTextHandleEnabled) { + return null; + } + // fallthrough + case HapticFeedbackConstants.CLOCK_TICK: + case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK: + return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK); + + case HapticFeedbackConstants.KEYBOARD_RELEASE: + case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE: + case HapticFeedbackConstants.ENTRY_BUMP: + case HapticFeedbackConstants.DRAG_CROSSING: + return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false); + + case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS + case HapticFeedbackConstants.VIRTUAL_KEY: + case HapticFeedbackConstants.EDGE_RELEASE: + case HapticFeedbackConstants.CALENDAR_DATE: + case HapticFeedbackConstants.CONFIRM: + case HapticFeedbackConstants.GESTURE_START: + case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: + case HapticFeedbackConstants.SCROLL_LIMIT: + return VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + + case HapticFeedbackConstants.LONG_PRESS: + case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: + case HapticFeedbackConstants.DRAG_START: + case HapticFeedbackConstants.EDGE_SQUEEZE: + return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); + + case HapticFeedbackConstants.REJECT: + return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); + + case HapticFeedbackConstants.SAFE_MODE_ENABLED: + return mSafeModeEnabledVibrationEffect; + + case HapticFeedbackConstants.ASSISTANT_BUTTON: + if (mVibrator.areAllPrimitivesSupported( + VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, + VibrationEffect.Composition.PRIMITIVE_TICK)) { + // quiet ramp, short pause, then sharp tick + return VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50) + .compose(); + } + // fallback for devices without composition support + return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); + + case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE: + return getScaledPrimitiveOrElseEffect( + VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f, + VibrationEffect.EFFECT_TEXTURE_TICK); + + case HapticFeedbackConstants.TOGGLE_ON: + return getScaledPrimitiveOrElseEffect( + VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, + VibrationEffect.EFFECT_TICK); + + case HapticFeedbackConstants.TOGGLE_OFF: + return getScaledPrimitiveOrElseEffect( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 0.2f, + VibrationEffect.EFFECT_TEXTURE_TICK); + + case HapticFeedbackConstants.NO_HAPTICS: + default: + return null; + } + } + + /** + * Provides the {@link VibrationAttributes} that should be used for a haptic feedback. + * + * @param effectId the haptic feedback effect ID whose respective vibration attributes we want + * to get. + * @param bypassVibrationIntensitySetting {@code true} if the returned attribute should bypass + * vibration intensity settings. {@code false} otherwise. + * @return the {@link VibrationAttributes} that should be used for the provided haptic feedback. + */ + public VibrationAttributes getVibrationAttributesForHapticFeedback( + int effectId, boolean bypassVibrationIntensitySetting) { + VibrationAttributes attrs; + switch (effectId) { + case HapticFeedbackConstants.EDGE_SQUEEZE: + case HapticFeedbackConstants.EDGE_RELEASE: + attrs = PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES; + break; + case HapticFeedbackConstants.ASSISTANT_BUTTON: + case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: + case HapticFeedbackConstants.SCROLL_TICK: + case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: + case HapticFeedbackConstants.SCROLL_LIMIT: + attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; + break; + default: + attrs = TOUCH_VIBRATION_ATTRIBUTES; + } + if (bypassVibrationIntensitySetting) { + attrs = new VibrationAttributes.Builder(attrs) + .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .build(); + } + return attrs; + } + + /** Dumps relevant state. */ + public void dump(String prefix, PrintWriter pw) { + pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled); + } + + private VibrationEffect getScaledPrimitiveOrElseEffect( + int primitiveId, float scale, int elseEffectId) { + if (mVibrator.areAllPrimitivesSupported(primitiveId)) { + return VibrationEffect.startComposition() + .addPrimitive(primitiveId, scale) + .compose(); + } else { + return VibrationEffect.get(elseEffectId); + } + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 4ae7c77c104e..dbd6bf461e85 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -698,7 +698,23 @@ final class VibrationSettings { @Nullable private VibrationEffect createEffectFromResource(int resId) { - long[] timings = getLongIntArray(mContext.getResources(), resId); + return createEffectFromResource(mContext.getResources(), resId); + } + + /** + * Provides a {@link VibrationEffect} from a timings-array provided as an int-array resource.. + * + * <p>If the timings array is {@code null} or empty, it returns {@code null}. + * + * <p>If the timings array has a size of one, it returns a one-shot vibration with duration that + * is equal to the single value in the array. + * + * <p>If the timings array has more than one values, it returns a non-repeating wave-form + * vibration with off-on timings as per the provided timings array. + */ + @Nullable + static VibrationEffect createEffectFromResource(Resources res, int resId) { + long[] timings = getLongIntArray(res, resId); return createEffectFromTimings(timings); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 799643444b3c..b55af76e3799 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1016,8 +1016,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub && mWallpaper.userId == mCurrentUserId) { Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + ", reverting to built-in wallpaper!"); - clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, - null); + clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); } } }; @@ -1197,7 +1196,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } else { // Timeout Slog.w(TAG, "Reverting to built-in wallpaper!"); - clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null); + clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); final String flattened = wpService.flattenToString(); EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED, flattened.substring(0, Math.min(flattened.length(), @@ -1236,8 +1235,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } else { if (mLmkLimitRebindRetries <= 0) { Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!"); - clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, - null); + clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES; return; } @@ -1256,7 +1254,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME > SystemClock.uptimeMillis()) { Slog.w(TAG, "Reverting to built-in wallpaper!"); - clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null); + clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); } else { mWallpaper.lastDiedTime = SystemClock.uptimeMillis(); tryToRebind(); @@ -1497,7 +1495,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper, null)) { Slog.w(TAG, "Wallpaper " + wpService + " no longer available; reverting to default"); - clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null); + clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); } } } @@ -1585,7 +1583,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null); + clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); } } } @@ -1606,7 +1604,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null); + clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); } } if (wallpaper.nextWallpaperComponent != null @@ -1638,7 +1636,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mShuttingDown = false; mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); - mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context); + ComponentName defaultComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context); + mDefaultWallpaperComponent = defaultComponent == null ? mImageWallpaper : defaultComponent; mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mIPackageManager = AppGlobals.getPackageManager(); @@ -1721,7 +1720,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.i(TAG, "Unable to regenerate crop; resetting"); } - clearWallpaperLocked(false, FLAG_SYSTEM, UserHandle.USER_SYSTEM, null); + clearWallpaperLocked(FLAG_SYSTEM, UserHandle.USER_SYSTEM, null); } } else { if (DEBUG) { @@ -1978,7 +1977,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (si == null) { - clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, reply); + clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, reply); } else { Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked"); // We might end up persisting the current wallpaper data @@ -2002,10 +2001,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (serviceInfo == null) { if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) { - clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null); - clearWallpaperLocked(false, FLAG_LOCK, wallpaper.userId, reply); + clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); + clearWallpaperLocked(FLAG_LOCK, wallpaper.userId, reply); } else { - clearWallpaperLocked(false, wallpaper.mWhich, wallpaper.userId, reply); + clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply); } return; } @@ -2038,9 +2037,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData data = null; synchronized (mLock) { if (mIsLockscreenLiveWallpaperEnabled) { - clearWallpaperLocked(callingPackage, false, which, userId); + clearWallpaperLocked(callingPackage, which, userId); } else { - clearWallpaperLocked(false, which, userId, null); + clearWallpaperLocked(which, userId, null); } if (which == FLAG_LOCK) { @@ -2058,8 +2057,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void clearWallpaperLocked(String callingPackage, boolean defaultFailed, - int which, int userId) { + private void clearWallpaperLocked(String callingPackage, int which, int userId) { // Might need to bring it in the first time to establish our rewrite if (!mWallpaperMap.contains(userId)) { @@ -2094,7 +2092,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub component = wallpaper.wallpaperComponent; finalWhich = FLAG_LOCK | FLAG_SYSTEM; } else { - component = defaultFailed ? mImageWallpaper : null; + component = null; finalWhich = which; } @@ -2114,8 +2112,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // TODO(b/266818039) remove this version of the method - private void clearWallpaperLocked(boolean defaultFailed, int which, int userId, - IRemoteCallback reply) { + private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) { if (which != FLAG_SYSTEM && which != FLAG_LOCK) { throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear"); } @@ -2168,9 +2165,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.primaryColors = null; wallpaper.imageWallpaperPending = false; if (userId != mCurrentUserId) return; - if (bindWallpaperComponentLocked(defaultFailed - ? mImageWallpaper - : null, true, false, wallpaper, reply)) { + if (bindWallpaperComponentLocked(null, true, false, wallpaper, reply)) { return; } } catch (IllegalArgumentException e1) { @@ -3523,11 +3518,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { if (componentName == null) { componentName = mDefaultWallpaperComponent; - if (componentName == null) { - // Fall back to static image wallpaper - componentName = mImageWallpaper; - if (DEBUG_LIVE) Slog.v(TAG, "No default component; using image wallpaper"); - } } int serviceUserId = wallpaper.userId; ServiceInfo si = mIPackageManager.getServiceInfo(componentName, @@ -3997,7 +3987,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mFallbackWallpaper = new WallpaperData(systemUserId, FLAG_SYSTEM); mFallbackWallpaper.allowBackup = false; mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked(); - bindWallpaperComponentLocked(mImageWallpaper, true, false, mFallbackWallpaper, null); + bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false, + mFallbackWallpaper, null); } } diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java index 56edde09f747..271d71ed0ec0 100644 --- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java +++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java @@ -24,6 +24,7 @@ import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityThread; @@ -40,7 +41,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -48,7 +48,6 @@ import android.util.DisplayMetrics; import android.util.Slog; import android.view.Display; import android.view.Gravity; -import android.view.IWindowManager; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -56,7 +55,6 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowInsets.Type; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.Button; @@ -95,6 +93,10 @@ public class ImmersiveModeConfirmation { */ @Nullable private Context mWindowContext; + /** + * The root display area feature id that the {@link #mWindowContext} is attaching to. + */ + private int mWindowContextRootDisplayAreaId = FEATURE_UNDEFINED; // Local copy of vr mode enabled state, to avoid calling into VrManager with // the lock held. private boolean mVrModeEnabled; @@ -206,12 +208,15 @@ public class ImmersiveModeConfirmation { private void handleHide() { if (mClingWindow != null) { if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation"); - // We don't care which root display area the window manager is specifying for removal. - try { - getWindowManager(FEATURE_UNDEFINED).removeView(mClingWindow); - } catch (WindowManager.InvalidDisplayException e) { - Slog.w(TAG, "Fail to hide the immersive confirmation window because of " + e); - return; + if (mWindowManager != null) { + try { + mWindowManager.removeView(mClingWindow); + } catch (WindowManager.InvalidDisplayException e) { + Slog.w(TAG, "Fail to hide the immersive confirmation window because of " + + e); + } + mWindowManager = null; + mWindowContext = null; } mClingWindow = null; } @@ -394,26 +399,18 @@ public class ImmersiveModeConfirmation { * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the * confirmation window. */ - private WindowManager getWindowManager(int rootDisplayAreaId) { - if (mWindowManager == null || mWindowContext == null) { - // Create window context to specify the RootDisplayArea - final Bundle options = getOptionsForWindowContext(rootDisplayAreaId); - mWindowContext = mContext.createWindowContext( - IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options); - mWindowManager = mWindowContext.getSystemService(WindowManager.class); - return mWindowManager; + @NonNull + private WindowManager createWindowManager(int rootDisplayAreaId) { + if (mWindowManager != null) { + throw new IllegalStateException( + "Must not create a new WindowManager while there is an existing one"); } - - // Update the window context and window manager to specify the RootDisplayArea + // Create window context to specify the RootDisplayArea final Bundle options = getOptionsForWindowContext(rootDisplayAreaId); - final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); - try { - wms.attachWindowContextToDisplayArea(mWindowContext.getWindowContextToken(), - IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, mContext.getDisplayId(), options); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - + mWindowContextRootDisplayAreaId = rootDisplayAreaId; + mWindowContext = mContext.createWindowContext( + IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options); + mWindowManager = mWindowContext.getSystemService(WindowManager.class); return mWindowManager; } @@ -434,14 +431,23 @@ public class ImmersiveModeConfirmation { } private void handleShow(int rootDisplayAreaId) { - if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation"); + if (mClingWindow != null) { + if (rootDisplayAreaId == mWindowContextRootDisplayAreaId) { + if (DEBUG) Slog.d(TAG, "Immersive mode confirmation has already been shown"); + return; + } else { + // Hide the existing confirmation before show a new one in the new root. + if (DEBUG) Slog.d(TAG, "Immersive mode confirmation was shown in a different root"); + handleHide(); + } + } + if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation"); mClingWindow = new ClingWindowView(mContext, mConfirm); - // show the confirmation - WindowManager.LayoutParams lp = getClingWindowLayoutParams(); + final WindowManager.LayoutParams lp = getClingWindowLayoutParams(); try { - getWindowManager(rootDisplayAreaId).addView(mClingWindow, lp); + createWindowManager(rootDisplayAreaId).addView(mClingWindow, lp); } catch (WindowManager.InvalidDisplayException e) { Slog.w(TAG, "Fail to show the immersive confirmation window because of " + e); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 0c1f33ccedbc..92c0987d5636 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1020,7 +1020,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final WindowContainer<?> parent = getParent(); final Task thisTask = asTask(); if (thisTask != null && parent.asTask() == null - && mTransitionController.isTransientHide(thisTask)) { + && mTransitionController.isTransientVisible(thisTask)) { // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule. return TASK_FRAGMENT_VISIBILITY_VISIBLE; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index a14354041b91..eaea53d555e2 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -407,6 +407,36 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } + /** Returns {@code true} if the task should keep visible if this is a transient transition. */ + boolean isTransientVisible(@NonNull Task task) { + if (mTransientLaunches == null) return false; + int occludedCount = 0; + final int numTransient = mTransientLaunches.size(); + for (int i = numTransient - 1; i >= 0; --i) { + final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask(); + if (transientRoot == null) continue; + final WindowContainer<?> rootParent = transientRoot.getParent(); + if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; + final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor + .mOpaqueActivityHelper.getOpaqueActivity(rootParent); + if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) { + occludedCount++; + } + } + if (occludedCount == numTransient) { + for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { + if (mTransientLaunches.keyAt(i).isDescendantOf(task)) { + // Keep transient activity visible until transition finished, so it won't pause + // with transient-hide tasks that may delay resuming the next top. + return true; + } + } + // Let transient-hide activities pause before transition is finished. + return false; + } + return isInTransientHide(task); + } + boolean canApplyDim(@NonNull Task task) { if (mTransientLaunches == null) return true; final Dimmer dimmer = task.getDimmer(); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index bfaf6fcd3110..dfaa17494855 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -477,15 +477,22 @@ class TransitionController { if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) { return true; } - for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { - if (mWaitingTransitions.get(i).isInTransientHide(task)) return true; - } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { if (mPlayingTransitions.get(i).isInTransientHide(task)) return true; } return false; } + boolean isTransientVisible(@NonNull Task task) { + if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) { + return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + if (mPlayingTransitions.get(i).isTransientVisible(task)) return true; + } + return false; + } + boolean canApplyDim(@Nullable Task task) { if (task == null) { // Always allow non-activity window. diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING index be7feb5c9bf7..648b904787ce 100644 --- a/services/incremental/TEST_MAPPING +++ b/services/incremental/TEST_MAPPING @@ -9,6 +9,9 @@ ] }, { + "name": "CtsPackageManagerIncrementalStatsHostTestCases" + }, + { "name": "CtsIncrementalInstallHostTestCases" }, { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java index 7b1654549841..a8b0a7b5633d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java @@ -40,6 +40,7 @@ import android.os.Build; import android.os.Process; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -169,14 +170,14 @@ public class ArchiveManagerTest { } @Test - public void archiveApp_callerNotInstallerOfRecord() { + public void archiveApp_noInstallerFound() { InstallSource otherInstallSource = InstallSource.create( CALLER_PACKAGE, CALLER_PACKAGE, - /* installerPackageName= */ "different", + /* installerPackageName= */ null, Binder.getCallingUid(), - CALLER_PACKAGE, + /* updateOwnerPackageName= */ null, /* installerAttributionTag= */ null, /* packageSource= */ 0); when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); @@ -187,29 +188,8 @@ public class ArchiveManagerTest { mIntentSender) ); assertThat(e).hasMessageThat().isEqualTo( - String.format("Caller is not the installer of record for %s.", PACKAGE)); - } - - @Test - public void archiveApp_callerNotUpdateOwner() { - InstallSource otherInstallSource = - InstallSource.create( - CALLER_PACKAGE, - CALLER_PACKAGE, - CALLER_PACKAGE, - Binder.getCallingUid(), - /* updateOwnerPackageName= */ "different", - /* installerAttributionTag= */ null, - /* packageSource= */ 0); - when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); - - Exception e = assertThrows( - SecurityException.class, - () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, - mIntentSender) - ); - assertThat(e).hasMessageThat().isEqualTo( - String.format("Caller is not the update owner for %s.", PACKAGE)); + TextUtils.formatSimple("No installer found to archive app %s.", + PACKAGE)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 32d0c98d4481..989aee06a1df 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -42,6 +43,8 @@ import static org.mockito.Mockito.when; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; import android.os.Handler; import android.os.Message; import android.os.UserHandle; @@ -67,11 +70,13 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.List; @@ -123,9 +128,10 @@ public class FullScreenMagnificationGestureHandlerTest { public static final int STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP = 8; public static final int STATE_PANNING = 9; public static final int STATE_SCALING_AND_PANNING = 10; + public static final int STATE_SINGLE_PANNING = 11; public static final int FIRST_STATE = STATE_IDLE; - public static final int LAST_STATE = STATE_SCALING_AND_PANNING; + public static final int LAST_STATE = STATE_SINGLE_PANNING; // Co-prime x and y, to potentially catch x-y-swapped errors public static final float DEFAULT_X = 301; @@ -155,6 +161,10 @@ public class FullScreenMagnificationGestureHandlerTest { private float mOriginalMagnificationPersistedScale; + static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 800, 800); + + static final Region INITIAL_MAGNIFICATION_REGION = new Region(INITIAL_MAGNIFICATION_BOUNDS); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -182,11 +192,19 @@ public class FullScreenMagnificationGestureHandlerTest { new MagnificationScaleProvider(mContext), () -> null, ConcurrentUtils.DIRECT_EXECUTOR) { - @Override - public boolean magnificationRegionContains(int displayId, float x, float y) { - return true; - } + @Override + public boolean magnificationRegionContains(int displayId, float x, float y) { + return true; + } }; + + doAnswer((Answer<Void>) invocationOnMock -> { + Object[] args = invocationOnMock.getArguments(); + Region regionArg = (Region) args[1]; + regionArg.set(new Rect(INITIAL_MAGNIFICATION_BOUNDS)); + return null; + }).when(mockWindowManager).getMagnificationRegion(anyInt(), any(Region.class)); + mFullScreenMagnificationController.register(DISPLAY_0); mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(true); mClock = new OffsettableClock.Stopped(); @@ -214,6 +232,7 @@ public class FullScreenMagnificationGestureHandlerTest { mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback, detectTripleTap, detectShortcutTrigger, mWindowMagnificationPromptController, DISPLAY_0); + h.setSinglePanningEnabled(true); mHandler = new TestHandler(h.mDetectingState, mClock) { @Override protected String messageToString(Message m) { @@ -239,6 +258,7 @@ public class FullScreenMagnificationGestureHandlerTest { * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE}) */ @Test + @Ignore("b/291925580") public void testEachState_isReachableAndRecoverable() { forEachState(state -> { goFromStateIdleTo(state); @@ -526,6 +546,75 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void testActionUpNotAtEdge_singlePanningState_detectingState() { + goFromStateIdleTo(STATE_SINGLE_PANNING); + + send(upEvent()); + + check(mMgh.mCurrentState == mMgh.mDetectingState, STATE_IDLE); + assertTrue(isZoomed()); + } + + @Test + public void testScroll_SinglePanningDisabled_delegatingState() { + mMgh.setSinglePanningEnabled(false); + + goFromStateIdleTo(STATE_ACTIVATED); + allowEventDelegation(); + swipeAndHold(); + + assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); + } + + @Test + public void testScroll_zoomedStateAndAtEdge_delegatingState() { + goFromStateIdleTo(STATE_ACTIVATED); + mFullScreenMagnificationController.setCenter( + DISPLAY_0, + INITIAL_MAGNIFICATION_BOUNDS.left, + INITIAL_MAGNIFICATION_BOUNDS.top / 2, + false, + 1); + final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; + PointF initCoords = + new PointF( + mFullScreenMagnificationController.getCenterX(DISPLAY_0), + mFullScreenMagnificationController.getCenterY(DISPLAY_0)); + PointF endCoords = new PointF(initCoords.x, initCoords.y); + endCoords.offset(swipeMinDistance, 0); + allowEventDelegation(); + + swipeAndHold(initCoords, endCoords); + + assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); + assertTrue(isZoomed()); + } + + @Test + public void testScroll_singlePanningAndAtEdge_delegatingState() { + goFromStateIdleTo(STATE_SINGLE_PANNING); + mFullScreenMagnificationController.setCenter( + DISPLAY_0, + INITIAL_MAGNIFICATION_BOUNDS.left, + INITIAL_MAGNIFICATION_BOUNDS.top / 2, + false, + 1); + final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; + PointF initCoords = + new PointF( + mFullScreenMagnificationController.getCenterX(DISPLAY_0), + mFullScreenMagnificationController.getCenterY(DISPLAY_0)); + PointF endCoords = new PointF(initCoords.x, initCoords.y); + endCoords.offset(swipeMinDistance, 0); + allowEventDelegation(); + + swipeAndHold(initCoords, endCoords); + + assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); + assertTrue(isZoomed()); + } + + @Test public void testShortcutTriggered_invokeShowWindowPromptAction() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); @@ -740,6 +829,10 @@ public class FullScreenMagnificationGestureHandlerTest { state); check(mMgh.mPanningScalingState.mScaling, state); } break; + case STATE_SINGLE_PANNING: { + check(isZoomed(), state); + check(mMgh.mCurrentState == mMgh.mSinglePanningState, state); + } break; default: throw new IllegalArgumentException("Illegal state: " + state); } } @@ -803,6 +896,10 @@ public class FullScreenMagnificationGestureHandlerTest { send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4)); send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 5)); } break; + case STATE_SINGLE_PANNING: { + goFromStateIdleTo(STATE_ACTIVATED); + swipeAndHold(); + } break; default: throw new IllegalArgumentException("Illegal state: " + state); } @@ -859,6 +956,10 @@ public class FullScreenMagnificationGestureHandlerTest { case STATE_SCALING_AND_PANNING: { returnToNormalFrom(STATE_PANNING); } break; + case STATE_SINGLE_PANNING: { + send(upEvent()); + returnToNormalFrom(STATE_ACTIVATED); + } break; default: throw new IllegalArgumentException("Illegal state: " + state); } } @@ -906,6 +1007,11 @@ public class FullScreenMagnificationGestureHandlerTest { send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2)); } + private void swipeAndHold(PointF start, PointF end) { + send(downEvent(start.x, start.y)); + send(moveEvent(end.x, end.y)); + } + private void longTap() { send(downEvent()); fastForward(2000); diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING new file mode 100644 index 000000000000..0ffa891ce3e1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.contentcapture" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING new file mode 100644 index 000000000000..419508ca5e17 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.contentprotection" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index a109d5cddd21..f552ab2dab60 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -406,7 +406,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { UriGrantsManagerInternal mUgmInternal; @Mock AppOpsManager mAppOpsManager; - private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener; @Mock private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; @@ -606,12 +605,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName, SEARCH_SELECTOR_PKG); - doAnswer(invocation -> { - mOnPermissionChangeListener = invocation.getArgument(2); - return null; - }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(), - any()); - mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper())); mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, @@ -3224,6 +3217,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testUpdateAppNotifyCreatorBlock() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); + Thread.sleep(500); + waitForIdle(); + + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); + } + + @Test + public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + + mBinderService.setNotificationsEnabledForPackage(PKG, 0, false); + verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null)); + } + + @Test + public void testUpdateAppNotifyCreatorUnblock() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true); + Thread.sleep(500); + waitForIdle(); + + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); + } + + @Test public void testUpdateChannelNotifyCreatorBlock() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), @@ -12139,134 +12174,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); } - @Test - public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage() - throws RemoteException { - // Have preexisting posted notifications from revoked package and other packages. - mService.addNotification(new NotificationRecord(mContext, - generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel)); - mService.addNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 2, 0), mTestNotificationChannel)); - // Have preexisting enqueued notifications from revoked package and other packages. - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel)); - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 4, 0), mTestNotificationChannel)); - assertThat(mService.mNotificationList).hasSize(2); - assertThat(mService.mEnqueuedNotifications).hasSize(2); - - when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0); - waitForIdle(); - - assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other"); - assertThat(mService.mEnqueuedNotifications).hasSize(1); - assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo( - "other"); - } - - @Test - public void onOpChanged_permissionStillGranted_notificationsAreNotAffected() - throws RemoteException { - // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission - // being now granted, AND having previously posted notifications from said package) should - // never happen (if we trust the broadcasts are correct). So this test is for a what-if - // scenario, to verify we still handle it reasonably. - - // Have preexisting posted notifications from specific package and other packages. - mService.addNotification(new NotificationRecord(mContext, - generateSbn("granted", 1001, 1, 0), mTestNotificationChannel)); - mService.addNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 2, 0), mTestNotificationChannel)); - // Have preexisting enqueued notifications from specific package and other packages. - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("granted", 1001, 3, 0), mTestNotificationChannel)); - mService.addEnqueuedNotification(new NotificationRecord(mContext, - generateSbn("other", 1002, 4, 0), mTestNotificationChannel)); - assertThat(mService.mNotificationList).hasSize(2); - assertThat(mService.mEnqueuedNotifications).hasSize(2); - - when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0); - waitForIdle(); - - assertThat(mService.mNotificationList).hasSize(2); - assertThat(mService.mEnqueuedNotifications).hasSize(2); - } - - @Test - public void onOpChanged_permissionGranted_notifiesAppUnblocked() throws Exception { - when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0); - waitForIdle(); - mTestableLooper.moveTimeForward(500); - waitForIdle(); - - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null)); - assertThat(captor.getValue().getAction()).isEqualTo( - NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED); - assertThat(captor.getValue().getPackage()).isEqualTo(PKG); - assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)).isFalse(); - } - - @Test - public void onOpChanged_permissionRevoked_notifiesAppBlocked() throws Exception { - when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false); - - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0); - waitForIdle(); - mTestableLooper.moveTimeForward(500); - waitForIdle(); - - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null)); - assertThat(captor.getValue().getAction()).isEqualTo( - NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED); - assertThat(captor.getValue().getPackage()).isEqualTo(PKG); - assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)).isTrue(); - } - - @Test - public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception { - mService.addNotification(new NotificationRecord(mContext, - generateSbn("package", 1001, 1, 0), mTestNotificationChannel)); - assertThat(mService.mNotificationList).hasSize(1); - when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001); - when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn( - true); - - // Start with granted permission and simulate effect of revoking it. - when(mPermissionHelper.hasPermission(1001)).thenReturn(true); - doAnswer(invocation -> { - when(mPermissionHelper.hasPermission(1001)).thenReturn(false); - mOnPermissionChangeListener.onOpChanged( - AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0); - return null; - }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true); - - mBinderService.setNotificationsEnabledForPackage("package", 1001, false); - waitForIdle(); - - assertThat(mService.mNotificationList).hasSize(0); - - mTestableLooper.moveTimeForward(500); - waitForIdle(); - verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null)); - } - private static <T extends Parcelable> T parcelAndUnparcel(T source, Parcelable.Creator<T> creator) { Parcel parcel = Parcel.obtain(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index dedb8f170ee0..3ee75de23fdb 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -771,7 +771,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mConfig = null; // will evaluate config to zen mode off for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -798,7 +798,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -825,7 +825,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -2269,7 +2269,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off // given that we don't have any zen rules active. mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.evaluateZenMode("test", true); + mZenModeHelper.evaluateZenModeLocked("test", true); // Check that the change actually took: zen mode should be off now assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index abf21a57dd40..7eab06ac8b95 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -656,7 +656,7 @@ public class RootTaskTests extends WindowTestsBase { topSplitPrimary.getVisibility(null /* starting */)); // Make primary split root transient-hide. spyOn(splitPrimary.mTransitionController); - doReturn(true).when(splitPrimary.mTransitionController).isTransientHide( + doReturn(true).when(splitPrimary.mTransitionController).isTransientVisible( organizer.mPrimary); // The split root and its top become visible. assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java index b181213df003..fb957485d9eb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java @@ -91,7 +91,7 @@ public class ScreenshotTests { } @Test - public void testScreenshotSecureLayers() { + public void testScreenshotSecureLayers() throws InterruptedException { SurfaceControl secureSC = new SurfaceControl.Builder() .setName("SecureChildSurfaceControl") .setBLASTLayer() @@ -197,6 +197,8 @@ public class ScreenshotTests { private static final long WAIT_TIMEOUT_S = 5; private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final CountDownLatch mAttachedLatch = new CountDownLatch(1); + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -204,7 +206,16 @@ public class ScreenshotTests { PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); } - SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) { + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + mAttachedLatch.countDown(); + } + + SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) + throws InterruptedException { + assertTrue("Failed to wait for onAttachedToWindow", + mAttachedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); SurfaceControl.Transaction t = new SurfaceControl.Transaction(); CountDownLatch countDownLatch = new CountDownLatch(1); mHandler.post(() -> { diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index ffecafb7b4ed..5154d17f2e6b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1488,6 +1488,47 @@ public class TransitionTests extends WindowTestsBase { } @Test + public void testIsTransientVisible() { + final ActivityRecord appB = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); + final ActivityRecord recent = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); + final ActivityRecord appA = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task taskA = appA.getTask(); + final Task taskB = appB.getTask(); + final Task taskRecent = recent.getTask(); + registerTestTransitionPlayer(); + final TransitionController controller = mRootWindowContainer.mTransitionController; + final Transition transition = createTestTransition(TRANSIT_OPEN, controller); + controller.moveToCollecting(transition); + transition.collect(recent); + transition.collect(taskA); + transition.setTransientLaunch(recent, taskA); + taskRecent.moveToFront("move-recent-to-front"); + + // During collecting and playing, the recent is on top so it is visible naturally. + // While B needs isTransientVisible to keep visibility because it is occluded by recents. + assertFalse(controller.isTransientVisible(taskB)); + assertTrue(controller.isTransientVisible(taskA)); + assertFalse(controller.isTransientVisible(taskRecent)); + // Switch to playing state. + transition.onTransactionReady(transition.getSyncId(), mMockT); + assertTrue(controller.isTransientVisible(taskA)); + + // Switch to another task. For example, use gesture navigation to switch tasks. + taskB.moveToFront("move-b-to-front"); + // The previous app (taskA) should be paused first so it loses transient visible. Because + // visually it is taskA -> taskB, the pause -> resume order should be the same. + assertFalse(controller.isTransientVisible(taskA)); + // Keep the recent visible so there won't be 2 activities pausing at the same time. It is + // to avoid the latency to resume the current top, i.e. appB. + assertTrue(controller.isTransientVisible(taskRecent)); + // The recent is paused after the transient transition is finished. + controller.finishTransition(transition); + assertFalse(controller.isTransientVisible(taskRecent)); + } + + @Test public void testNotReadyPushPop() { final TransitionController controller = new TestTransitionController(mAtm); controller.setSyncEngine(mWm.mSyncEngine); diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt index 0f406fda28b6..845e64949230 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt @@ -70,6 +70,9 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {} + @FlakyTest(bugId = 291575593) + override fun entireScreenCovered() {} + @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {} @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 7a16060e3370..94b090f42c9b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -200,7 +200,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @FlakyTest(bugId = 251217585) + @FlakyTest(bugId = 285980483) @Test override fun focusChanges() { super.focusChanges() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 4164c0d440c0..df9780ef8b98 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -21,6 +21,7 @@ import android.app.WallpaperManager import android.content.res.Resources import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.ComponentNameMatcher.Companion.SPLASH_SCREEN import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER @@ -190,6 +191,16 @@ class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) { } } + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(launchNewTaskApp) + ) + } + } + companion object { private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher { val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext) |