diff options
137 files changed, 2650 insertions, 975 deletions
diff --git a/cmds/appops/OWNERS b/cmds/appops/OWNERS index 999ea0e62a0a..2fe78c5a7092 100644 --- a/cmds/appops/OWNERS +++ b/cmds/appops/OWNERS @@ -1 +1,2 @@ +#Bug component: 137825 include /core/java/android/permission/OWNERS diff --git a/core/api/current.txt b/core/api/current.txt index ff011cf356c0..eaefe84beb1f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9694,6 +9694,7 @@ package android.content { method public int describeContents(); method public void enforceCallingUid(); method @Nullable public String getAttributionTag(); + method public int getDeviceId(); method @Nullable public android.content.AttributionSource getNext(); method @Nullable public String getPackageName(); method public int getPid(); @@ -9709,6 +9710,7 @@ package android.content { ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource build(); method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String); + method @NonNull public android.content.AttributionSource.Builder setDeviceId(int); method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 43303865e01a..083c445407dc 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -850,6 +850,7 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); + ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); method public void enforceCallingPid(); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4eaebe07777f..59b0dacd7cd7 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -27,7 +27,6 @@ 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; @@ -3277,8 +3276,7 @@ class ContextImpl extends Context { // if this Context is not a WindowContext. WindowContext finalization is handled in // WindowContext class. if (mToken instanceof WindowTokenClient && mOwnsToken) { - WindowTokenClientController.getInstance().detachIfNeeded( - (WindowTokenClient) mToken); + ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); } super.finalize(); } @@ -3306,7 +3304,7 @@ class ContextImpl extends Context { final WindowTokenClient token = new WindowTokenClient(); final ContextImpl context = systemContext.createWindowContextBase(token, displayId); token.attachContext(context); - WindowTokenClientController.getInstance().attachToDisplayContent(token, displayId); + token.attachToDisplayContent(displayId); context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI; context.mOwnsToken = true; @@ -3465,7 +3463,7 @@ class ContextImpl extends Context { AttributionSource attributionSource = new AttributionSource(Process.myUid(), Process.myPid(), mOpPackageName, attributionTag, (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null, - nextAttributionSource); + getDeviceId(), nextAttributionSource); // If we want to access protected data on behalf of another app we need to // tell the OS that we opt in to participate in the attribution chain. if (nextAttributionSource != null) { diff --git a/core/java/android/app/servertransaction/WindowTokenClientController.java b/core/java/android/app/servertransaction/WindowTokenClientController.java deleted file mode 100644 index 28e2040de9d5..000000000000 --- a/core/java/android/app/servertransaction/WindowTokenClientController.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.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 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 overrideInstance(@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; - } - onWindowContainerTokenAttached(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; - } - onWindowContainerTokenAttached(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 onWindowContainerTokenAttached(@NonNull WindowTokenClient client, int displayId, - @NonNull Configuration configuration) { - synchronized (mLock) { - mWindowTokenClientMap.put(client.asBinder(), client); - } - client.onConfigurationChanged(configuration, displayId, - false /* shouldReportConfigChange */); - } -} diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 3dddbc0fb90e..d0bb2b9b6eeb 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -115,14 +115,14 @@ public final class AttributionSource implements Parcelable { public AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token) { this(uid, Process.INVALID_PID, packageName, attributionTag, token, - /*renouncedPermissions*/ null, /*next*/ null); + /*renouncedPermissions*/ null, Context.DEVICE_ID_DEFAULT, /*next*/ null); } /** @hide */ public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token) { this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null, - /*next*/ null); + Context.DEVICE_ID_DEFAULT, /*next*/ null); } /** @hide */ @@ -132,21 +132,23 @@ public final class AttributionSource implements Parcelable { @Nullable AttributionSource next) { this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken, (renouncedPermissions != null) - ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next); + ? renouncedPermissions.toArray(new String[0]) : null, Context.DEVICE_ID_DEFAULT, + /*next*/ next); } /** @hide */ public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) { this(current.getUid(), current.getPid(), current.getPackageName(), current.getAttributionTag(), current.getToken(), - current.mAttributionSourceState.renouncedPermissions, next); + current.mAttributionSourceState.renouncedPermissions, current.getDeviceId(), next); } /** @hide */ public AttributionSource(int uid, int pid, @Nullable String packageName, - @Nullable String attributionTag, @Nullable String[] renouncedPermissions, + @Nullable String attributionTag, @Nullable String[] renouncedPermissions, int deviceId, @Nullable AttributionSource next) { - this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next); + this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, deviceId, + next); } /** @hide */ @@ -155,6 +157,16 @@ public final class AttributionSource implements Parcelable { @Nullable String attributionTag, @NonNull IBinder token, @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) { + this(uid, pid, packageName, attributionTag, token, renouncedPermissions, + Context.DEVICE_ID_DEFAULT, next); + } + + /** @hide */ + @TestApi + public AttributionSource(int uid, int pid, @Nullable String packageName, + @Nullable String attributionTag, @NonNull IBinder token, + @Nullable String[] renouncedPermissions, + int deviceId, @Nullable AttributionSource next) { mAttributionSourceState = new AttributionSourceState(); mAttributionSourceState.uid = uid; mAttributionSourceState.pid = pid; @@ -162,6 +174,7 @@ public final class AttributionSource implements Parcelable { mAttributionSourceState.packageName = packageName; mAttributionSourceState.attributionTag = attributionTag; mAttributionSourceState.renouncedPermissions = renouncedPermissions; + mAttributionSourceState.deviceId = deviceId; mAttributionSourceState.next = (next != null) ? new AttributionSourceState[] {next.mAttributionSourceState} : new AttributionSourceState[0]; } @@ -197,25 +210,31 @@ public final class AttributionSource implements Parcelable { /** @hide */ public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) { return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(), - getToken(), mAttributionSourceState.renouncedPermissions, next); + getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), next); } /** @hide */ public AttributionSource withPackageName(@Nullable String packageName) { return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(), - getToken(), mAttributionSourceState.renouncedPermissions, getNext()); + getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext()); } /** @hide */ public AttributionSource withToken(@NonNull Binder token) { return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(), - token, mAttributionSourceState.renouncedPermissions, getNext()); + token, mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext()); } /** @hide */ public AttributionSource withPid(int pid) { return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(), - getToken(), mAttributionSourceState.renouncedPermissions, getNext()); + getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext()); + } + + /** @hide */ + public AttributionSource withDeviceId(int deviceId) { + return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(), + getToken(), mAttributionSourceState.renouncedPermissions, deviceId, getNext()); } /** @hide */ @@ -259,6 +278,7 @@ public final class AttributionSource implements Parcelable { try { return new AttributionSource.Builder(uid) .setPid(Process.myPid()) + .setDeviceId(Context.DEVICE_ID_DEFAULT) .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0]) .build(); } catch (Exception ignored) { @@ -497,6 +517,13 @@ public final class AttributionSource implements Parcelable { } /** + * The device ID for which permissions are checked. + */ + public int getDeviceId() { + return mAttributionSourceState.deviceId; + } + + /** * Unique token for that source. * * @hide @@ -662,6 +689,19 @@ public final class AttributionSource implements Parcelable { } /** + * Set the device ID for this attribution source, permission check would happen + * against this device ID. + * + * @return the builder + */ + public @NonNull Builder setDeviceId(int deviceId) { + checkNotUsed(); + mBuilderFieldsSet |= 0x12; + mAttributionSourceState.deviceId = deviceId; + return this; + } + + /** * The next app to receive the permission protected data. * * @deprecated Use {@link setNextAttributionSource} instead. @@ -703,6 +743,9 @@ public final class AttributionSource implements Parcelable { if ((mBuilderFieldsSet & 0x10) == 0) { mAttributionSourceState.renouncedPermissions = null; } + if ((mBuilderFieldsSet & 0x12) == 0) { + mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT; + } if ((mBuilderFieldsSet & 0x20) == 0) { mAttributionSourceState.next = null; } diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS index 28a242037f6a..0809de25b45c 100644 --- a/core/java/android/print/OWNERS +++ b/core/java/android/print/OWNERS @@ -1,4 +1,4 @@ # Bug component: 47273 -svetoslavganov@android.com -svetoslavganov@google.com +anothermark@google.com +kumarashishg@google.com diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS deleted file mode 100644 index 28a242037f6a..000000000000 --- a/core/java/android/print/pdf/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# Bug component: 47273 - -svetoslavganov@android.com -svetoslavganov@google.com diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS index 28a242037f6a..0809de25b45c 100644 --- a/core/java/android/printservice/OWNERS +++ b/core/java/android/printservice/OWNERS @@ -1,4 +1,4 @@ # Bug component: 47273 -svetoslavganov@android.com -svetoslavganov@google.com +anothermark@google.com +kumarashishg@google.com diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS deleted file mode 100644 index 28a242037f6a..000000000000 --- a/core/java/android/printservice/recommendation/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# Bug component: 47273 - -svetoslavganov@android.com -svetoslavganov@google.com 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/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index eb270e2fb2f0..4b9a957f541d 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -21,7 +21,6 @@ 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; @@ -105,8 +104,7 @@ public class WindowContextController { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea( - mToken, type, displayId, options) + mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options) ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED; if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) { Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:" @@ -142,13 +140,13 @@ public class WindowContextController { throw new IllegalStateException("The Window Context should have been attached" + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea); } - WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken); + mToken.attachToWindowToken(windowToken); } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) { - WindowTokenClientController.getInstance().detachIfNeeded(mToken); + mToken.detachFromWindowContainerIfNeeded(); mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED; if (DEBUG_ATTACH) { Log.d(TAG, "Detach Window Context."); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 632208cdb97c..849e0b32591a 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.res.TypedArray; import android.os.Handler; import android.os.RemoteException; import android.os.SystemProperties; @@ -33,6 +34,7 @@ import android.view.IWindowSession; import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -62,6 +64,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { .getInt("persist.wm.debug.predictive_back", 1) != 0; private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties .getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0; + private static final boolean PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE = + SystemProperties.getInt("persist.wm.debug.predictive_back_fallback_window_attribute", 0) + != 0; @Nullable private ImeOnBackInvokedDispatcher mImeDispatcher; @@ -500,6 +505,31 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { applicationInfo.packageName, requestsPredictiveBack)); } + + if (PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE && !requestsPredictiveBack) { + // Compatibility check for legacy window style flag used by Wear OS. + // Note on compatibility behavior: + // 1. windowSwipeToDismiss should be respected for all apps not opted in. + // 2. windowSwipeToDismiss should be true for all apps not opted in, which + // enables the PB animation for them. + // 3. windowSwipeToDismiss=false should be respected for apps not opted in, + // which disables PB & onBackPressed caused by BackAnimController's + // setTrigger(true) + TypedArray windowAttr = + context.obtainStyledAttributes( + new int[] {android.R.attr.windowSwipeToDismiss}); + boolean windowSwipeToDismiss = true; + if (windowAttr.getIndexCount() > 0) { + windowSwipeToDismiss = windowAttr.getBoolean(0, true); + } + windowAttr.recycle(); + + if (DEBUG) { + Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss); + } + + requestsPredictiveBack = windowSwipeToDismiss; + } } return requestsPredictiveBack; diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 55b823be3cb8..a208634abe78 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -23,10 +23,10 @@ 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,9 +36,14 @@ 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; @@ -65,11 +70,15 @@ 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(); /** @@ -92,6 +101,88 @@ 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} @@ -116,14 +207,15 @@ 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 WindowTokenClientController#attachToDisplayArea} - * or {@link WindowTokenClientController#attachToDisplayContent}. + * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)} + * or {@link #attachToDisplayContent(int)}. * * @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(); diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml index 03d3637b150d..529f18b78e4d 100644 --- a/core/res/res/values-watch/config_material.xml +++ b/core/res/res/values-watch/config_material.xml @@ -30,9 +30,6 @@ <!-- Always overscan by default to ensure onApplyWindowInsets will always be called. --> <bool name="config_windowOverscanByDefault">true</bool> - <!-- Enable windowSwipeToDismiss. --> - <bool name="config_windowSwipeToDismiss">true</bool> - <!-- Style the scrollbars accoridngly. --> <drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</drawable> <drawable name="config_scrollbarTrackVertical">@drawable/scrollbar_vertical_track</drawable> 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 467d555f161f..a52d2e88145f 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -24,13 +24,11 @@ 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; @@ -58,8 +56,6 @@ import org.mockito.MockitoAnnotations; public class WindowContextControllerTest { private WindowContextController mController; @Mock - private WindowTokenClientController mWindowTokenClientController; - @Mock private WindowTokenClient mMockToken; @Before @@ -67,9 +63,7 @@ public class WindowContextControllerTest { MockitoAnnotations.initMocks(this); mController = new WindowContextController(mMockToken); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); - WindowTokenClientController.overrideInstance(mWindowTokenClientController); - doReturn(true).when(mWindowTokenClientController).attachToDisplayArea( - eq(mMockToken), anyInt(), anyInt(), any()); + doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any()); } @Test(expected = IllegalStateException.class) @@ -84,7 +78,7 @@ public class WindowContextControllerTest { public void testDetachIfNeeded_NotAttachedYet_DoNothing() { mController.detachIfNeeded(); - verify(mWindowTokenClientController, never()).detachIfNeeded(any()); + verify(mMockToken, never()).detachFromWindowContainerIfNeeded(); } @Test diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index aa0a8d9fc06a..3206dd2123d5 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -257,6 +257,7 @@ applications that come with the platform <permission name="android.permission.CLEAR_APP_CACHE"/> <permission name="android.permission.ACCESS_INSTANT_APPS" /> <permission name="android.permission.CONNECTIVITY_INTERNAL"/> + <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" /> <permission name="android.permission.DELETE_CACHE_FILES"/> <permission name="android.permission.DELETE_PACKAGES"/> <permission name="android.permission.DUMP"/> diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java index 2b1515af9d07..3bb2564807b6 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java @@ -18,7 +18,6 @@ package android.security.keystore2; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.PackageManager; import android.hardware.security.keymint.KeyParameter; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyProperties; @@ -300,12 +299,6 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase return false; } - private static boolean hasKeyMintV2() { - PackageManager pm = android.app.AppGlobals.getInitialApplication().getPackageManager(); - return pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE, 200) - && !pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE, 300); - } - @Override protected final void addAlgorithmSpecificParametersToBegin( @NonNull List<KeyParameter> parameters, Authorization[] keyCharacteristics) { @@ -314,12 +307,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest )); // Only add the KM_TAG_RSA_OAEP_MGF_DIGEST tag to begin() if the MGF Digest is - // present in the key properties or KeyMint version is 200. Keys generated prior to - // Android 14 did not have this tag (Keystore didn't add it) and hence not present in - // imported key as well, so specifying any MGF digest tag would cause a begin() - // operation (on an Android 14 device) to fail (with a key that was generated on - // Android 13 or below). - if (isMgfDigestTagPresentInKeyProperties(keyCharacteristics) || hasKeyMintV2()) { + // present in the key properties. Keys generated prior to Android 14 did not have + // this tag (Keystore didn't add it) so specifying any MGF digest tag would cause + // a begin() operation (on an Android 14 device) to fail (with a key that was generated + // on Android 13 or below). + if (isMgfDigestTagPresentInKeyProperties(keyCharacteristics)) { parameters.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mKeymasterMgf1Digest )); 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/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt index fd56a6e49d3e..8a3c2c975faa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt @@ -42,7 +42,7 @@ import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary import org.junit.Assert.assertNotNull -internal object SplitScreenUtils { +object SplitScreenUtils { private const val TIMEOUT_MS = 3_000L private const val DRAG_DURATION_MS = 1_000L private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" 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/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java index 3505cfb9d38a..74f04e093162 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java @@ -33,8 +33,6 @@ import android.view.View; import androidx.annotation.Nullable; -import java.io.File; - /** * Installation failed: Return status code to the caller or display failure UI to user */ @@ -101,14 +99,8 @@ public class InstallFailed extends AlertActivity { // Set header icon and title PackageUtil.AppSnippet as; PackageManager pm = getPackageManager(); - - if ("package".equals(packageURI.getScheme())) { - as = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo), - pm.getApplicationIcon(appInfo)); - } else { - final File sourceFile = new File(packageURI.getPath()); - as = PackageUtil.getAppSnippet(this, appInfo, sourceFile); - } + as = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET, + PackageUtil.AppSnippet.class); // Store label for dialog mLabel = as.label; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index 7bea33971259..1088acef0fb0 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -16,6 +16,7 @@ package com.android.packageinstaller; +import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_APP_SNIPPET; import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID; import android.app.PendingIntent; @@ -86,7 +87,8 @@ public class InstallInstalling extends AlertActivity { // ContentResolver.SCHEME_FILE // STAGED_SESSION_ID extra contains an ID of a previously staged install session. final File sourceFile = new File(mPackageURI.getPath()); - PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile); + PackageUtil.AppSnippet as = getIntent() + .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class); mAlert.setIcon(as.icon); mAlert.setTitle(as.label); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java index ff991d2f7ee3..fbc9525d4615 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; @@ -30,7 +29,6 @@ import android.widget.Button; import androidx.annotation.Nullable; -import java.io.File; import java.util.List; /** @@ -65,18 +63,8 @@ public class InstallSuccess extends AlertActivity { ApplicationInfo appInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); mAppPackageName = appInfo.packageName; - Uri packageURI = intent.getData(); - - // Set header icon and title - PackageManager pm = getPackageManager(); - - if ("package".equals(packageURI.getScheme())) { - mAppSnippet = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo), - pm.getApplicationIcon(appInfo)); - } else { - File sourceFile = new File(packageURI.getPath()); - mAppSnippet = PackageUtil.getAppSnippet(this, appInfo, sourceFile); - } + mAppSnippet = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET, + PackageUtil.AppSnippet.class); mLaunchIntent = getPackageManager().getLaunchIntentForPackage(mAppPackageName); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index bc7fa02e497c..b66679af3fe3 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -84,6 +84,7 @@ public class PackageInstallerActivity extends AlertActivity { static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG"; static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO"; static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID"; + static final String EXTRA_APP_SNIPPET = "EXTRA_APP_SNIPPET"; private static final String ALLOW_UNKNOWN_SOURCES_KEY = PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY"; @@ -708,6 +709,9 @@ public class PackageInstallerActivity extends AlertActivity { if (stagedSessionId > 0) { newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId); } + if (mAppSnippet != null) { + newIntent.putExtra(EXTRA_APP_SNIPPET, mAppSnippet); + } newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI); startActivity(newIntent); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index c2d4f18d1c7c..5880a29c6c46 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -28,8 +28,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; import android.os.UserHandle; import android.util.Log; import android.view.View; @@ -117,7 +122,7 @@ public class PackageUtil { icon); } - static final class AppSnippet { + static final class AppSnippet implements Parcelable { @NonNull public CharSequence label; @Nullable public Drawable icon; public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) { @@ -125,10 +130,52 @@ public class PackageUtil { this.icon = icon; } + private AppSnippet(Parcel in) { + label = in.readString(); + Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class); + icon = new BitmapDrawable(Resources.getSystem(), bmp); + } + @Override public String toString() { return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]"; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(label.toString()); + Bitmap bmp = getBitmapFromDrawable(icon); + dest.writeParcelable(bmp, 0); + } + + private Bitmap getBitmapFromDrawable(Drawable drawable) { + // Create an empty bitmap with the dimensions of our drawable + final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + // Associate it with a canvas. This canvas will draw the icon on the bitmap + final Canvas canvas = new Canvas(bmp); + // Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the + // bitmap held within + drawable.draw(canvas); + + return bmp; + } + + public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() { + public AppSnippet createFromParcel(Parcel in) { + return new AppSnippet(in); + } + + public AppSnippet[] newArray(int size) { + return new AppSnippet[size]; + } + }; } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index f522fd13c9f8..2118d2cbf4b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -356,11 +356,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> connectDevice(); } - public HearingAidInfo getHearingAidInfo() { - return mHearingAidInfo; - } - - public void setHearingAidInfo(HearingAidInfo hearingAidInfo) { + void setHearingAidInfo(HearingAidInfo hearingAidInfo) { mHearingAidInfo = hearingAidInfo; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 0c1b793102bf..441d3a52b97f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -20,6 +20,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.ScanFilter; import android.content.Context; import android.util.Log; @@ -114,10 +115,21 @@ public class CachedBluetoothDeviceManager { /** * Create and return a new {@link CachedBluetoothDevice}. This assumes * that {@link #findDevice} has already been called and returned null. - * @param device the address of the new Bluetooth device + * @param device the new Bluetooth device * @return the newly created CachedBluetoothDevice object */ public CachedBluetoothDevice addDevice(BluetoothDevice device) { + return addDevice(device, /*leScanFilters=*/null); + } + + /** + * Create and return a new {@link CachedBluetoothDevice}. This assumes + * that {@link #findDevice} has already been called and returned null. + * @param device the new Bluetooth device + * @param leScanFilters the BLE scan filters which the device matched + * @return the newly created CachedBluetoothDevice object + */ + public CachedBluetoothDevice addDevice(BluetoothDevice device, List<ScanFilter> leScanFilters) { CachedBluetoothDevice newDevice; final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); synchronized (this) { @@ -125,7 +137,7 @@ public class CachedBluetoothDeviceManager { if (newDevice == null) { newDevice = new CachedBluetoothDevice(mContext, profileManager, device); mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice); - mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice); + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice, leScanFilters); if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice) && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) { mCachedDevices.add(newDevice); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java index e5e57824f6ef..efba953e3c6b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -18,10 +18,13 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.le.ScanFilter; import android.content.ContentResolver; import android.content.Context; import android.media.AudioDeviceAttributes; import android.media.audiopolicy.AudioProductStrategy; +import android.os.ParcelUuid; import android.provider.Settings; import android.util.Log; @@ -59,7 +62,8 @@ public class HearingAidDeviceManager { mRoutingHelper = routingHelper; } - void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) { + void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice, + List<ScanFilter> leScanFilters) { long hiSyncId = getHiSyncId(newDevice.getDevice()); if (isValidHiSyncId(hiSyncId)) { // Once hiSyncId is valid, assign hearing aid info @@ -68,6 +72,21 @@ public class HearingAidDeviceManager { .setAshaDeviceMode(getDeviceMode(newDevice.getDevice())) .setHiSyncId(hiSyncId); newDevice.setHearingAidInfo(infoBuilder.build()); + } else if (leScanFilters != null && !newDevice.isHearingAidDevice()) { + // If the device is added with hearing aid scan filter during pairing, set an empty + // hearing aid info to indicate it's a hearing aid device. The info will be updated + // when corresponding profiles connected. + for (ScanFilter leScanFilter: leScanFilters) { + final ParcelUuid serviceUuid = leScanFilter.getServiceUuid(); + final ParcelUuid serviceDataUuid = leScanFilter.getServiceDataUuid(); + if (BluetoothUuid.HEARING_AID.equals(serviceUuid) + || BluetoothUuid.HAS.equals(serviceUuid) + || BluetoothUuid.HEARING_AID.equals(serviceDataUuid) + || BluetoothUuid.HAS.equals(serviceDataUuid)) { + newDevice.setHearingAidInfo(new HearingAidInfo.Builder().build()); + break; + } + } } } 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/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java index 0d5de88cc394..ea10944be0e9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java @@ -33,6 +33,8 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.le.ScanFilter; import android.content.Context; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; @@ -147,7 +149,7 @@ public class HearingAidDeviceManagerTest { HearingAidProfile.DeviceSide.SIDE_RIGHT); assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1); - mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1); + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null); assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1); assertThat(mCachedDevice1.getDeviceMode()).isEqualTo( @@ -164,12 +166,43 @@ public class HearingAidDeviceManagerTest { when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn( BluetoothHearingAid.HI_SYNC_ID_INVALID); - mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1); + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null); verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class)); } /** + * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an + * empty hearing aid info on the device. + */ + @Test + public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn( + BluetoothHearingAid.HI_SYNC_ID_INVALID); + final ScanFilter scanFilter = new ScanFilter.Builder() + .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build(); + + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter)); + + assertThat(mCachedDevice1.isHearingAidDevice()).isTrue(); + } + + /** + * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set + * hearing aid info on the device. + */ + @Test + public void initHearingAidDeviceIfNeeded_randomScanFilter_setHearingAidInfo() { + when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn( + BluetoothHearingAid.HI_SYNC_ID_INVALID); + final ScanFilter scanFilter = new ScanFilter.Builder().build(); + + mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter)); + + assertThat(mCachedDevice1.isHearingAidDevice()).isFalse(); + } + + /** * Test setSubDeviceIfNeeded, a device with same HiSyncId will be set as sub device */ @Test diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 7d8b06663f78..368115b99040 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -320,6 +320,7 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> <uses-permission android:name="android.permission.OBSERVE_APP_USAGE" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt index 50e5466d0325..1cb8e43cf2c8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt @@ -12,12 +12,9 @@ class KeyguardClockFrame( ) : FrameLayout(context, attrs) { private var drawAlpha: Int = 255 - init { - setLayerType(View.LAYER_TYPE_SOFTWARE, null) - } - protected override fun onSetAlpha(alpha: Int): Boolean { - drawAlpha = alpha + // Ignore alpha passed from View, prefer to compute it from set values + drawAlpha = (255 * this.alpha * transitionAlpha).toInt() return true } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt index 0e224060a36f..f3a07fc53027 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt @@ -16,15 +16,52 @@ package com.android.systemui.dreams -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.core.LogLevel -import com.android.systemui.log.dagger.DreamLog -import javax.inject.Inject +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer /** Logs dream-related stuff to a {@link LogBuffer}. */ -class DreamLogger @Inject constructor(@DreamLog private val buffer: LogBuffer) { - /** Logs a debug message to the buffer. */ - fun d(tag: String, message: String) { - buffer.log(tag, LogLevel.DEBUG, { str1 = message }, { message }) - } +class DreamLogger(buffer: MessageBuffer, tag: String) : Logger(buffer, tag) { + fun logDreamOverlayEnabled(enabled: Boolean) = + d({ "Dream overlay enabled: $bool1" }) { bool1 = enabled } + + fun logIgnoreAddComplication(reason: String, complication: String) = + d({ "Ignore adding complication, reason: $str1, complication: $str2" }) { + str1 = reason + str2 = complication + } + + fun logIgnoreRemoveComplication(reason: String, complication: String) = + d({ "Ignore removing complication, reason: $str1, complication: $str2" }) { + str1 = reason + str2 = complication + } + + fun logAddComplication(complication: String) = + d({ "Add dream complication: $str1" }) { str1 = complication } + + fun logRemoveComplication(complication: String) = + d({ "Remove dream complication: $str1" }) { str1 = complication } + + fun logOverlayActive(active: Boolean) = d({ "Dream overlay active: $bool1" }) { bool1 = active } + + fun logLowLightActive(active: Boolean) = + d({ "Low light mode active: $bool1" }) { bool1 = active } + + fun logHasAssistantAttention(hasAttention: Boolean) = + d({ "Dream overlay has Assistant attention: $bool1" }) { bool1 = hasAttention } + + fun logStatusBarVisible(visible: Boolean) = + d({ "Dream overlay status bar visible: $bool1" }) { bool1 = visible } + + fun logAvailableComplicationTypes(types: Int) = + d({ "Available complication types: $int1" }) { int1 = types } + + fun logShouldShowComplications(showComplications: Boolean) = + d({ "Dream overlay should show complications: $bool1" }) { bool1 = showComplications } + + fun logShowOrHideStatusBarItem(show: Boolean, type: String) = + d({ "${if (bool1) "Showing" else "Hiding"} dream status bar item: $int1" }) { + bool1 = show + str1 = type + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index 484bf3d51f36..01fb5227749f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -36,6 +36,9 @@ import com.android.systemui.complication.ComplicationLayoutParams.Position import com.android.systemui.dreams.dagger.DreamOverlayModule import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.DreamLog import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.policy.ConfigurationController @@ -65,12 +68,14 @@ constructor( private val mDreamInTranslationYDistance: Int, @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION) private val mDreamInTranslationYDurationMs: Long, - private val mLogger: DreamLogger, + @DreamLog logBuffer: LogBuffer, ) { companion object { private const val TAG = "DreamOverlayAnimationsController" } + private val logger = Logger(logBuffer, TAG) + private var mAnimator: Animator? = null private lateinit var view: View @@ -179,11 +184,11 @@ constructor( doOnEnd { mAnimator = null mOverlayStateController.setEntryAnimationsFinished(true) - mLogger.d(TAG, "Dream overlay entry animations finished.") + logger.d("Dream overlay entry animations finished.") } - doOnCancel { mLogger.d(TAG, "Dream overlay entry animations canceled.") } + doOnCancel { logger.d("Dream overlay entry animations canceled.") } start() - mLogger.d(TAG, "Dream overlay entry animations started.") + logger.d("Dream overlay entry animations started.") } } @@ -242,11 +247,11 @@ constructor( doOnEnd { mAnimator = null mOverlayStateController.setExitAnimationsRunning(false) - mLogger.d(TAG, "Dream overlay exit animations finished.") + logger.d("Dream overlay exit animations finished.") } - doOnCancel { mLogger.d(TAG, "Dream overlay exit animations canceled.") } + doOnCancel { logger.d("Dream overlay exit animations canceled.") } start() - mLogger.d(TAG, "Dream overlay exit animations started.") + logger.d("Dream overlay exit animations started.") } mOverlayStateController.setExitAnimationsRunning(true) return mAnimator as AnimatorSet diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index c2421dcbc6ca..c9748f954670 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -28,6 +28,8 @@ 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.log.LogBuffer; +import com.android.systemui.log.dagger.DreamLog; import com.android.systemui.statusbar.policy.CallbackController; import java.util.ArrayList; @@ -115,10 +117,10 @@ public class DreamOverlayStateController implements public DreamOverlayStateController(@Main Executor executor, @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled, FeatureFlags featureFlags, - DreamLogger dreamLogger) { + @DreamLog LogBuffer logBuffer) { mExecutor = executor; mOverlayEnabled = overlayEnabled; - mLogger = dreamLogger; + mLogger = new DreamLogger(logBuffer, TAG); mFeatureFlags = featureFlags; if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) { mSupportedTypes = Complication.COMPLICATION_TYPE_NONE @@ -126,7 +128,7 @@ public class DreamOverlayStateController implements } else { mSupportedTypes = Complication.COMPLICATION_TYPE_NONE; } - mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled); + mLogger.logDreamOverlayEnabled(mOverlayEnabled); } /** @@ -134,14 +136,13 @@ public class DreamOverlayStateController implements */ public void addComplication(Complication complication) { if (!mOverlayEnabled) { - mLogger.d(TAG, - "Ignoring adding complication due to overlay disabled: " + complication); + mLogger.logIgnoreAddComplication("overlay disabled", complication.toString()); return; } mExecutor.execute(() -> { if (mComplications.add(complication)) { - mLogger.d(TAG, "Added dream complication: " + complication); + mLogger.logAddComplication(complication.toString()); mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged()); } }); @@ -152,14 +153,13 @@ public class DreamOverlayStateController implements */ public void removeComplication(Complication complication) { if (!mOverlayEnabled) { - mLogger.d(TAG, - "Ignoring removing complication due to overlay disabled: " + complication); + mLogger.logIgnoreRemoveComplication("overlay disabled", complication.toString()); return; } mExecutor.execute(() -> { if (mComplications.remove(complication)) { - mLogger.d(TAG, "Removed dream complication: " + complication); + mLogger.logRemoveComplication(complication.toString()); mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged()); } }); @@ -305,7 +305,7 @@ public class DreamOverlayStateController implements * @param active {@code true} if overlay is active, {@code false} otherwise. */ public void setOverlayActive(boolean active) { - mLogger.d(TAG, "Dream overlay active: " + active); + mLogger.logOverlayActive(active); modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE); } @@ -314,7 +314,7 @@ public class DreamOverlayStateController implements * @param active {@code true} if low light mode is active, {@code false} otherwise. */ public void setLowLightActive(boolean active) { - mLogger.d(TAG, "Low light mode active: " + active); + mLogger.logLowLightActive(active); if (isLowLightActive() && !active) { // Notify that we're exiting low light only on the transition from active to not active. @@ -346,7 +346,7 @@ public class DreamOverlayStateController implements * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise. */ public void setHasAssistantAttention(boolean hasAttention) { - mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention); + mLogger.logHasAssistantAttention(hasAttention); modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION); } @@ -355,7 +355,7 @@ public class DreamOverlayStateController implements * @param visible {@code true} if the status bar is visible, {@code false} otherwise. */ public void setDreamOverlayStatusBarVisible(boolean visible) { - mLogger.d(TAG, "Dream overlay status bar visible: " + visible); + mLogger.logStatusBarVisible(visible); modifyState( visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE); } @@ -373,7 +373,7 @@ public class DreamOverlayStateController implements */ public void setAvailableComplicationTypes(@Complication.ComplicationType int types) { mExecutor.execute(() -> { - mLogger.d(TAG, "Available complication types: " + types); + mLogger.logAvailableComplicationTypes(types); mAvailableComplicationTypes = types; mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged); }); @@ -391,7 +391,7 @@ public class DreamOverlayStateController implements */ public void setShouldShowComplications(boolean shouldShowComplications) { mExecutor.execute(() -> { - mLogger.d(TAG, "Should show complications: " + shouldShowComplications); + mLogger.logShouldShowComplications(shouldShowComplications); mShouldShowComplications = shouldShowComplications; mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged); }); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 3a284083e844..a6401b6594ba 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -36,6 +36,8 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem; import com.android.systemui.dreams.dagger.DreamOverlayComponent; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.dagger.DreamLog; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; @@ -161,7 +163,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve DreamOverlayStatusBarItemsProvider statusBarItemsProvider, DreamOverlayStateController dreamOverlayStateController, UserTracker userTracker, - DreamLogger dreamLogger) { + @DreamLog LogBuffer logBuffer) { super(view); mResources = resources; mMainExecutor = mainExecutor; @@ -177,7 +179,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mZenModeController = zenModeController; mDreamOverlayStateController = dreamOverlayStateController; mUserTracker = userTracker; - mLogger = dreamLogger; + mLogger = new DreamLogger(logBuffer, TAG); // Register to receive show/hide updates for the system status bar. Our custom status bar // needs to hide when the system status bar is showing to ovoid overlapping status bars. @@ -346,8 +348,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve @Nullable String contentDescription) { mMainExecutor.execute(() -> { if (mIsAttached) { - mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: " - + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType)); + mLogger.logShowOrHideStatusBarItem( + show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType)); mView.showIcon(iconType, show, contentDescription); } }); 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/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/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 0b6c7c415599..ff3e77c46f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -325,6 +325,9 @@ constructor( private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) : LockPatternUtils.StrongAuthTracker(context) { + private val selectedUserId = + userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged() + // Backing field for onStrongAuthRequiredChanged private val _authFlags = MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))) @@ -336,15 +339,12 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont ) val currentUserAuthFlags: Flow<AuthenticationFlags> = - userRepository.selectedUserInfo - .map { it.id } - .distinctUntilChanged() - .flatMapLatest { userId -> - _authFlags - .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) } - .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") } - .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) } - } + selectedUserId.flatMapLatest { userId -> + _authFlags + .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) } + .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") } + .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) } + } /** isStrongBiometricAllowed for the current user. */ val isStrongBiometricAllowed: Flow<Boolean> = @@ -352,16 +352,17 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont /** isNonStrongBiometricAllowed for the current user. */ val isNonStrongBiometricAllowed: Flow<Boolean> = - userRepository.selectedUserInfo - .map { it.id } - .distinctUntilChanged() + selectedUserId .flatMapLatest { userId -> _nonStrongBiometricAllowed .filter { it.first == userId } .map { it.second } - .onEach { Log.d(TAG, "isNonStrongBiometricAllowed changed for current user") } + .onEach { + Log.d(TAG, "isNonStrongBiometricAllowed changed for current user: $it") + } .onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) } } + .and(isStrongBiometricAllowed) private val currentUserId get() = userRepository.getSelectedUserInfo().id @@ -387,3 +388,6 @@ private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean = private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean = (getKeyguardDisabledFeatures(null, userId) and policy) == 0 + +private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> = + this.combine(anotherFlow) { a, b -> a && b } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 616ce39d3fee..9bec30052476 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -28,7 +28,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus -import com.android.systemui.keyguard.shared.model.FailedFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus @@ -214,7 +214,7 @@ constructor( ) { sendUpdateIfFingerprint( biometricSourceType, - FailedFingerprintAuthenticationStatus, + FailFingerprintAuthenticationStatus, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt new file mode 100644 index 000000000000..c849b8495a26 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt @@ -0,0 +1,138 @@ +/* + * 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.keyguard.domain.interactor + +import android.content.res.Resources +import android.hardware.biometrics.BiometricSourceType +import android.hardware.biometrics.BiometricSourceType.FINGERPRINT +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus +import com.android.systemui.keyguard.util.IndicationHelper +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map + +/** + * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for + * authentication events that should never surface a message to the user at the current device + * state. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class BiometricMessageInteractor +@Inject +constructor( + @Main private val resources: Resources, + private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + private val fingerprintPropertyRepository: FingerprintPropertyRepository, + private val indicationHelper: IndicationHelper, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + val fingerprintErrorMessage: Flow<BiometricMessage> = + fingerprintAuthRepository.authenticationStatus + .filter { + it is ErrorFingerprintAuthenticationStatus && + !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId) + } + .map { + val errorStatus = it as ErrorFingerprintAuthenticationStatus + BiometricMessage( + FINGERPRINT, + BiometricMessageType.ERROR, + errorStatus.msgId, + errorStatus.msg, + ) + } + + val fingerprintHelpMessage: Flow<BiometricMessage> = + fingerprintAuthRepository.authenticationStatus + .filter { it is HelpFingerprintAuthenticationStatus } + .filterNot { isPrimaryAuthRequired() } + .map { + val helpStatus = it as HelpFingerprintAuthenticationStatus + BiometricMessage( + FINGERPRINT, + BiometricMessageType.HELP, + helpStatus.msgId, + helpStatus.msg, + ) + } + + val fingerprintFailMessage: Flow<BiometricMessage> = + isUdfps().flatMapLatest { isUdfps -> + fingerprintAuthRepository.authenticationStatus + .filter { it is FailFingerprintAuthenticationStatus } + .filterNot { isPrimaryAuthRequired() } + .map { + BiometricMessage( + FINGERPRINT, + BiometricMessageType.FAIL, + BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED, + if (isUdfps) { + resources.getString( + com.android.internal.R.string.fingerprint_udfps_error_not_match + ) + } else { + resources.getString( + com.android.internal.R.string.fingerprint_error_not_match + ) + }, + ) + } + } + + private fun isUdfps() = + fingerprintPropertyRepository.sensorType.map { + it == FingerprintSensorType.UDFPS_OPTICAL || + it == FingerprintSensorType.UDFPS_ULTRASONIC + } + + private fun isPrimaryAuthRequired(): Boolean { + // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong + // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to + // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the + // check of whether non-strong biometric is allowed since strong biometrics can still be + // used. + return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) + } +} + +data class BiometricMessage( + val source: BiometricSourceType, + val type: BiometricMessageType, + val id: Int, + val message: String?, +) + +enum class BiometricMessageType { + HELP, + ERROR, + FAIL, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt index 5fb2cbf16a51..7fc6016bf087 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt @@ -41,7 +41,7 @@ data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) : FingerprintAuthenticationStatus() /** Fingerprint authentication failed message. */ -object FailedFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() +object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() /** Fingerprint authentication error message */ data class ErrorFingerprintAuthenticationStatus( 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/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/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt index 1cf9c1e1f299..1c5aa3cce266 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 @@ -46,14 +46,14 @@ interface Roundable { @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 */ @@ -335,13 +335,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/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/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/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/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/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java index 461ec653d819..40f0ed3626db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java @@ -28,10 +28,10 @@ import androidx.lifecycle.Observer; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.dreams.DreamLogger; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.core.FakeLogBuffer; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -57,8 +57,6 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase { private FakeFeatureFlags mFeatureFlags; @Mock private Observer mObserver; - @Mock - private DreamLogger mLogger; @Before public void setUp() { @@ -70,7 +68,7 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase { mExecutor, /* overlayEnabled= */ true, mFeatureFlags, - mLogger); + FakeLogBuffer.Factory.Companion.create()); mLiveData = new ComplicationCollectionLiveData(mStateController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index a00e5456b711..57307fc84b1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -9,6 +9,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.complication.ComplicationHostViewController import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.mockito.argumentCaptor @@ -47,7 +48,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { @Mock private lateinit var stateController: DreamOverlayStateController @Mock private lateinit var configController: ConfigurationController @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel - @Mock private lateinit var logger: DreamLogger + private val logBuffer = FakeLogBuffer.Factory.create() private lateinit var controller: DreamOverlayAnimationsController @Before @@ -66,7 +67,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { DREAM_IN_COMPLICATIONS_ANIMATION_DURATION, DREAM_IN_TRANSLATION_Y_DISTANCE, DREAM_IN_TRANSLATION_Y_DURATION, - logger + logBuffer ) val mockView: View = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 2c1ebe4121af..44a78ac3bc96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -34,6 +34,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.complication.Complication; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.FakeLogBuffer; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -58,8 +60,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; - @Mock - private DreamLogger mLogger; + private final LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create(); final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @@ -408,6 +409,11 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { } private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) { - return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger); + return new DreamOverlayStateController( + mExecutor, + overlayEnabled, + mFeatureFlags, + mLogBuffer + ); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 5dc0e55632fd..4e74f451932b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -48,6 +48,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.FakeLogBuffer; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.NextAlarmController; @@ -113,8 +115,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { DreamOverlayStateController mDreamOverlayStateController; @Mock UserTracker mUserTracker; - @Mock - DreamLogger mLogger; + + LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create(); @Captor private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor; @@ -149,7 +151,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mDreamOverlayStatusBarItemsProvider, mDreamOverlayStateController, mUserTracker, - mLogger); + mLogBuffer); } @Test @@ -293,7 +295,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mDreamOverlayStatusBarItemsProvider, mDreamOverlayStateController, mUserTracker, - mLogger); + mLogBuffer); controller.onViewAttached(); verify(mView, never()).showIcon( eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index f9070b37ca48..c6a2fa50b446 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -162,11 +162,11 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { @Test fun convenienceBiometricAllowedChange() = testScope.runTest { + overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false) createBiometricSettingsRepository() val convenienceBiometricAllowed = collectLastValue(underTest.isNonStrongBiometricAllowed) runCurrent() - onNonStrongAuthChanged(true, PRIMARY_USER_ID) assertThat(convenienceBiometricAllowed()).isTrue() @@ -175,6 +175,45 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { onNonStrongAuthChanged(false, PRIMARY_USER_ID) assertThat(convenienceBiometricAllowed()).isFalse() + mContext.orCreateTestableResources.removeOverride( + com.android.internal.R.bool.config_strongAuthRequiredOnBoot + ) + } + + @Test + fun whenStrongAuthRequiredAfterBoot_nonStrongBiometricNotAllowed() = + testScope.runTest { + overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, true) + createBiometricSettingsRepository() + + val convenienceBiometricAllowed = + collectLastValue(underTest.isNonStrongBiometricAllowed) + runCurrent() + onNonStrongAuthChanged(true, PRIMARY_USER_ID) + + assertThat(convenienceBiometricAllowed()).isFalse() + mContext.orCreateTestableResources.removeOverride( + com.android.internal.R.bool.config_strongAuthRequiredOnBoot + ) + } + + @Test + fun whenStrongBiometricAuthIsNotAllowed_nonStrongBiometrics_alsoNotAllowed() = + testScope.runTest { + overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false) + createBiometricSettingsRepository() + + val convenienceBiometricAllowed = + collectLastValue(underTest.isNonStrongBiometricAllowed) + runCurrent() + onNonStrongAuthChanged(true, PRIMARY_USER_ID) + assertThat(convenienceBiometricAllowed()).isTrue() + + onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, PRIMARY_USER_ID) + assertThat(convenienceBiometricAllowed()).isFalse() + mContext.orCreateTestableResources.removeOverride( + com.android.internal.R.bool.config_strongAuthRequiredOnBoot + ) } private fun onStrongAuthChanged(flags: Int, userId: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index a73b57c3bba2..def016ad8381 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt @@ -28,7 +28,7 @@ import com.android.systemui.biometrics.AuthController import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus -import com.android.systemui.keyguard.shared.model.FailedFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.util.mockito.whenever @@ -210,7 +210,7 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { ) assertThat(authenticationStatus) - .isInstanceOf(FailedFingerprintAuthenticationStatus::class.java) + .isInstanceOf(FailFingerprintAuthenticationStatus::class.java) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt new file mode 100644 index 000000000000..3389fa9a48af --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt @@ -0,0 +1,260 @@ +/* + * 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.keyguard.domain.interactor + +import android.hardware.biometrics.BiometricSourceType.FINGERPRINT +import android.hardware.fingerprint.FingerprintManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus +import com.android.systemui.keyguard.util.IndicationHelper +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class BiometricMessageInteractorTest : SysuiTestCase() { + + private lateinit var underTest: BiometricMessageInteractor + private lateinit var testScope: TestScope + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + + @Mock private lateinit var indicationHelper: IndicationHelper + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + testScope = TestScope() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + underTest = + BiometricMessageInteractor( + mContext.resources, + fingerprintAuthRepository, + fingerprintPropertyRepository, + indicationHelper, + keyguardUpdateMonitor, + ) + } + + @Test + fun fingerprintErrorMessage() = + testScope.runTest { + val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage) + + // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed + whenever( + indicationHelper.shouldSuppressErrorMsg( + FINGERPRINT, + FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE + ) + ) + .thenReturn(false) + + // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE + fingerprintAuthRepository.setAuthenticationStatus( + ErrorFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, + msg = "test" + ) + ) + + // THEN fingerprintErrorMessage is updated + assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR) + assertThat(fingerprintErrorMessage?.id) + .isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) + assertThat(fingerprintErrorMessage?.message).isEqualTo("test") + } + + @Test + fun fingerprintErrorMessage_suppressedError() = + testScope.runTest { + val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage) + + // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed + whenever( + indicationHelper.shouldSuppressErrorMsg( + FINGERPRINT, + FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE + ) + ) + .thenReturn(true) + + // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE + fingerprintAuthRepository.setAuthenticationStatus( + ErrorFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, + msg = "test" + ) + ) + + // THEN fingerprintErrorMessage isn't update - it's still null + assertThat(fingerprintErrorMessage).isNull() + } + + @Test + fun fingerprintHelpMessage() = + testScope.runTest { + val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage) + + // GIVEN primary auth is NOT required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + + // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY + fingerprintAuthRepository.setAuthenticationStatus( + HelpFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY, + msg = "test" + ) + ) + + // THEN fingerprintHelpMessage is updated + assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP) + assertThat(fingerprintHelpMessage?.id) + .isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY) + assertThat(fingerprintHelpMessage?.message).isEqualTo("test") + } + + @Test + fun fingerprintHelpMessage_primaryAuthRequired() = + testScope.runTest { + val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage) + + // GIVEN primary auth is required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(false) + + // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY + fingerprintAuthRepository.setAuthenticationStatus( + HelpFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY, + msg = "test" + ) + ) + + // THEN fingerprintHelpMessage isn't update - it's still null + assertThat(fingerprintHelpMessage).isNull() + } + + @Test + fun fingerprintFailMessage_nonUdfps() = + testScope.runTest { + val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage) + + // GIVEN primary auth is NOT required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + + // GIVEN rear fingerprint (not UDFPS) + fingerprintPropertyRepository.setProperties( + 0, + SensorStrength.STRONG, + FingerprintSensorType.REAR, + mapOf() + ) + + // WHEN authentication status fail + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + + // THEN fingerprintFailMessage is updated + assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL) + assertThat(fingerprintFailMessage?.id) + .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED) + assertThat(fingerprintFailMessage?.message) + .isEqualTo( + mContext.resources.getString( + com.android.internal.R.string.fingerprint_error_not_match + ) + ) + } + + @Test + fun fingerprintFailMessage_udfps() = + testScope.runTest { + val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage) + + // GIVEN primary auth is NOT required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + + // GIVEN UDFPS + fingerprintPropertyRepository.setProperties( + 0, + SensorStrength.STRONG, + FingerprintSensorType.UDFPS_OPTICAL, + mapOf() + ) + + // WHEN authentication status fail + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + + // THEN fingerprintFailMessage is updated to udfps message + assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT) + assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL) + assertThat(fingerprintFailMessage?.id) + .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED) + assertThat(fingerprintFailMessage?.message) + .isEqualTo( + mContext.resources.getString( + com.android.internal.R.string.fingerprint_udfps_error_not_match + ) + ) + } + + @Test + fun fingerprintFailedMessage_primaryAuthRequired() = + testScope.runTest { + val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage) + + // GIVEN primary auth is required + whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(false) + + // WHEN authentication status fail + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + + // THEN fingerprintFailedMessage isn't update - it's still null + assertThat(fingerprintFailedMessage).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt new file mode 100644 index 000000000000..272d686e974b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt @@ -0,0 +1,49 @@ +/* + * 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.log.core + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogMessageImpl +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import org.mockito.Mockito.anyString + +/** + * A fake [LogBuffer] used for testing that obtains a real [LogMessage] to prevent a + * [NullPointerException]. + */ +class FakeLogBuffer private constructor() { + class Factory private constructor() { + companion object { + fun create(): LogBuffer { + val logBuffer = mock<LogBuffer>() + whenever( + logBuffer.obtain( + tag = anyString(), + level = any(), + messagePrinter = any(), + exception = nullable(), + ) + ) + .thenReturn(LogMessageImpl.Factory.create()) + return logBuffer + } + } + } +} 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/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/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 8012c2653277..c9769b3f9932 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19915,7 +19915,8 @@ public class ActivityManagerService extends IActivityManager.Stub return superImpl.apply(code, new AttributionSource(shellUid, Process.INVALID_PID, "com.android.shell", attributionSource.getAttributionTag(), attributionSource.getToken(), - /*renouncedPermissions*/ null, attributionSource.getNext()), + /*renouncedPermissions*/ null, attributionSource.getDeviceId(), + attributionSource.getNext()), shouldCollectAsyncNotedOp, message, shouldCollectMessage, skiProxyOperation); } finally { @@ -19968,7 +19969,8 @@ public class ActivityManagerService extends IActivityManager.Stub return superImpl.apply(clientId, code, new AttributionSource(shellUid, Process.INVALID_PID, "com.android.shell", attributionSource.getAttributionTag(), attributionSource.getToken(), - /*renouncedPermissions*/ null, attributionSource.getNext()), + /*renouncedPermissions*/ null, attributionSource.getDeviceId(), + attributionSource.getNext()), startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); @@ -19994,7 +19996,8 @@ public class ActivityManagerService extends IActivityManager.Stub superImpl.apply(clientId, code, new AttributionSource(shellUid, Process.INVALID_PID, "com.android.shell", attributionSource.getAttributionTag(), attributionSource.getToken(), - /*renouncedPermissions*/ null, attributionSource.getNext()), + /*renouncedPermissions*/ null, attributionSource.getDeviceId(), + attributionSource.getNext()), skipProxyOperation); } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/appop/OWNERS b/services/core/java/com/android/server/appop/OWNERS index 999ea0e62a0a..2fe78c5a7092 100644 --- a/services/core/java/com/android/server/appop/OWNERS +++ b/services/core/java/com/android/server/appop/OWNERS @@ -1 +1,2 @@ +#Bug component: 137825 include /core/java/android/permission/OWNERS diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index ecb21d010897..b3ef86994159 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -118,7 +118,6 @@ import android.system.keystore2.Domain; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.EventLog; import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; @@ -836,9 +835,6 @@ public class LockSettingsService extends ILockSettings.Stub { @Override // binder interface public void systemReady() { - if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) { - EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), ""); // SafetyNet - } checkWritePermission(); mHasSecureLockScreen = mContext.getPackageManager() @@ -1093,9 +1089,6 @@ public class LockSettingsService extends ILockSettings.Stub { } private final void checkPasswordHavePermission() { - if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) { - EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), ""); // SafetyNet - } mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsHave"); } 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/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 7609073e149c..b01a89e672be 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1230,8 +1230,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt, - permission, attributionSource.getUid(), - attributionSource.getRenouncedPermissions())) { + permission, attributionSource)) { return PermissionChecker.PERMISSION_HARD_DENIED; } @@ -1292,12 +1291,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } case AppOpsManager.MODE_DEFAULT: { if (!skipCurrentChecks && !checkPermission(context, - permissionManagerServiceInt, permission, attributionSource.getUid(), - attributionSource.getRenouncedPermissions())) { + permissionManagerServiceInt, permission, attributionSource)) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (next != null && !checkPermission(context, permissionManagerServiceInt, - permission, next.getUid(), next.getRenouncedPermissions())) { + permission, next)) { return PermissionChecker.PERMISSION_HARD_DENIED; } } @@ -1326,8 +1324,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and // every attributionSource in the chain is registered with the system. final boolean isChainStartTrusted = !hasChain || checkPermission(context, - permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(), - current.getRenouncedPermissions()); + permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current); while (true) { final boolean skipCurrentChecks = (fromDatasource || next != null); @@ -1342,12 +1339,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { // If we already checked the permission for this one, skip the work if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt, - permission, current.getUid(), current.getRenouncedPermissions())) { + permission, current)) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (next != null && !checkPermission(context, permissionManagerServiceInt, - permission, next.getUid(), next.getRenouncedPermissions())) { + permission, next)) { return PermissionChecker.PERMISSION_HARD_DENIED; } @@ -1415,9 +1412,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { private static boolean checkPermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, - @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) { - boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, - uid) == PackageManager.PERMISSION_GRANTED; + @NonNull String permission, AttributionSource attributionSource) { + int uid = attributionSource.getUid(); + int deviceId = attributionSource.getDeviceId(); + final Context deviceContext = context.getDeviceId() == deviceId ? context + : context.createDeviceContext(deviceId); + boolean permissionGranted = deviceContext.checkPermission(permission, + Process.INVALID_PID, uid) == PackageManager.PERMISSION_GRANTED; // Override certain permissions checks for the shared isolated process for both // HotwordDetectionService and VisualQueryDetectionService, which ordinarily cannot hold @@ -1433,10 +1434,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { permissionGranted = hotwordServiceProvider != null && uid == hotwordServiceProvider.getUid(); } - + Set<String> renouncedPermissions = attributionSource.getRenouncedPermissions(); if (permissionGranted && renouncedPermissions.contains(permission) - && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS, - /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) { + && deviceContext.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS, + Process.INVALID_PID, uid) == PackageManager.PERMISSION_GRANTED) { return false; } return permissionGranted; @@ -1507,8 +1508,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and // every attributionSource in the chain is registered with the system. final boolean isChainStartTrusted = !hasChain || checkPermission(context, - permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(), - current.getRenouncedPermissions()); + permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current); while (true) { final boolean skipCurrentChecks = (next != null); diff --git a/services/core/java/com/android/server/vibrator/TEST_MAPPING b/services/core/java/com/android/server/vibrator/TEST_MAPPING new file mode 100644 index 000000000000..f0a7e470f8fd --- /dev/null +++ b/services/core/java/com/android/server/vibrator/TEST_MAPPING @@ -0,0 +1,21 @@ +{ + "presubmit": [ + { + "name": "FrameworksVibratorServicesTests", + "options": [ + {"exclude-annotation": "android.platform.test.annotations.LargeTest"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksVibratorServicesTests", + "options": [ + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ] +} 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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index c5f63ced989c..a6d5c19395b0 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -285,9 +285,9 @@ class ActivityMetricsLogger { final LaunchingState mLaunchingState; /** The type can be cold (new process), warm (new activity), or hot (bring to front). */ - final int mTransitionType; + int mTransitionType; /** Whether the process was already running when the transition started. */ - final boolean mProcessRunning; + boolean mProcessRunning; /** whether the process of the launching activity didn't have any active activity. */ final boolean mProcessSwitch; /** The process state of the launching activity prior to the launch */ @@ -972,6 +972,19 @@ class ActivityMetricsLogger { // App isn't attached to record yet, so match with info. if (info.mLastLaunchedActivity.info.applicationInfo == appInfo) { info.mBindApplicationDelayMs = info.calculateCurrentDelay(); + if (info.mProcessRunning) { + // It was HOT/WARM launch, but the process was died somehow right after the + // launch request. + info.mProcessRunning = false; + info.mTransitionType = TYPE_TRANSITION_COLD_LAUNCH; + final String msg = "Process " + info.mLastLaunchedActivity.info.processName + + " restarted"; + Slog.i(TAG, msg); + if (info.mLaunchingState.mTraceName != null) { + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg + "#" + + LaunchingState.sTraceSeqId); + } + } } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e30673cb1f45..ea06b4295850 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7991,6 +7991,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastReportedConfiguration.getMergedConfiguration())) { ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */, false /* ignoreVisibility */, true /* isRequestedOrientationChanged */); + if (mTransitionController.inPlayingTransition(this)) { + mTransitionController.mValidateActivityCompat.add(this); + } } mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( @@ -9410,6 +9413,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (info.applicationInfo == null) { return info.getMinAspectRatio(); } + if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) { + return mLetterboxUiController.getUserMinAspectRatio(); + } if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) { return info.getMinAspectRatio(); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 39f75703d71f..394105a646f1 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -40,6 +40,12 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ActivityInfo.isFixedOrientation; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.screenOrientationToString; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; @@ -103,6 +109,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; +import android.os.RemoteException; import android.util.Slog; import android.view.InsetsSource; import android.view.InsetsState; @@ -237,6 +244,10 @@ final class LetterboxUiController { // Counter for ActivityRecord#setRequestedOrientation private int mSetOrientationRequestCounter = 0; + // The min aspect ratio override set by user + @PackageManager.UserMinAspectRatio + private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET; + // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @@ -1059,7 +1070,7 @@ final class LetterboxUiController { private float getDefaultMinAspectRatioForUnresizableApps() { if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled() - || mActivityRecord.getDisplayContent() == null) { + || mActivityRecord.getDisplayArea() == null) { return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() @@ -1071,8 +1082,8 @@ final class LetterboxUiController { float getSplitScreenAspectRatio() { // Getting the same aspect ratio that apps get in split screen. - final DisplayContent displayContent = mActivityRecord.getDisplayContent(); - if (displayContent == null) { + final DisplayArea displayArea = mActivityRecord.getDisplayArea(); + if (displayArea == null) { return getDefaultMinAspectRatioForUnresizableApps(); } int dividerWindowWidth = @@ -1080,7 +1091,7 @@ final class LetterboxUiController { int dividerInsets = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); int dividerSize = dividerWindowWidth - dividerInsets * 2; - final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds()); + final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds()); if (bounds.width() >= bounds.height()) { bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); bounds.right = bounds.centerX(); @@ -1091,14 +1102,57 @@ final class LetterboxUiController { return computeAspectRatio(bounds); } + boolean shouldApplyUserMinAspectRatioOverride() { + if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()) { + return false; + } + + try { + final int userAspectRatio = mActivityRecord.mAtmService.getPackageManager() + .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId); + mUserAspectRatio = userAspectRatio; + return userAspectRatio != USER_MIN_ASPECT_RATIO_UNSET; + } catch (RemoteException e) { + // Don't apply user aspect ratio override + Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e); + return false; + } + } + + float getUserMinAspectRatio() { + switch (mUserAspectRatio) { + case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE: + return getDisplaySizeMinAspectRatio(); + case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN: + return getSplitScreenAspectRatio(); + case USER_MIN_ASPECT_RATIO_16_9: + return 16 / 9f; + case USER_MIN_ASPECT_RATIO_4_3: + return 4 / 3f; + case USER_MIN_ASPECT_RATIO_3_2: + return 3 / 2f; + default: + throw new AssertionError("Unexpected user min aspect ratio override: " + + mUserAspectRatio); + } + } + + private float getDisplaySizeMinAspectRatio() { + final DisplayArea displayArea = mActivityRecord.getDisplayArea(); + if (displayArea == null) { + return mActivityRecord.info.getMinAspectRatio(); + } + final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds()); + return computeAspectRatio(bounds); + } + private float getDefaultMinAspectRatio() { - final DisplayContent displayContent = mActivityRecord.getDisplayContent(); - if (displayContent == null + if (mActivityRecord.getDisplayArea() == null || !mLetterboxConfiguration .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) { return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); } - return computeAspectRatio(new Rect(displayContent.getBounds())); + return getDisplaySizeMinAspectRatio(); } Resources getResources() { diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index b71d918b987f..f33ecaa90531 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -684,6 +684,26 @@ class RecentTasks { } } + /** + * Removes the oldest recent task that is compatible with the given one. This is possible if + * the task windowing mode changed after being added to the Recents. + */ + void removeCompatibleRecentTask(Task task) { + final int taskIndex = mTasks.indexOf(task); + if (taskIndex < 0) { + return; + } + + final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */); + if (candidateIndex == -1) { + // Nothing to trim + return; + } + + final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex); + remove(taskToRemove); + } + void removeTasksByPackageName(String packageName, int userId) { for (int i = mTasks.size() - 1; i >= 0; --i) { final Task task = mTasks.get(i); @@ -1546,6 +1566,10 @@ class RecentTasks { * list (if any). */ private int findRemoveIndexForAddTask(Task task) { + return findRemoveIndexForTask(task, true /* includingSelf */); + } + + private int findRemoveIndexForTask(Task task, boolean includingSelf) { final int recentsCount = mTasks.size(); final Intent intent = task.intent; final boolean document = intent != null && intent.isDocument(); @@ -1601,6 +1625,8 @@ class RecentTasks { // existing task continue; } + } else if (!includingSelf) { + continue; } return i; } 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 881eddc03243..dfaa17494855 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.WindowConfiguration; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; @@ -141,6 +142,14 @@ class TransitionController { final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>(); /** + * List of activity-level participants. ActivityRecord is not expected to change independently, + * however, recent compatibility logic can now cause this at arbitrary times determined by + * client code. If it happens during an animation, the surface can be left at the wrong spot. + * TODO(b/290237710) remove when compat logic is moved. + */ + final ArrayList<ActivityRecord> mValidateActivityCompat = new ArrayList<>(); + + /** * Currently playing transitions (in the order they were started). When finished, records are * removed from this list. */ @@ -468,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. @@ -896,6 +912,14 @@ class TransitionController { } } mValidateCommitVis.clear(); + for (int i = 0; i < mValidateActivityCompat.size(); ++i) { + ActivityRecord ar = mValidateActivityCompat.get(i); + if (ar.getSurfaceControl() == null) continue; + final Point tmpPos = new Point(); + ar.getRelativePosition(tmpPos); + ar.getSyncTransaction().setPosition(ar.getSurfaceControl(), tmpPos.x, tmpPos.y); + } + mValidateActivityCompat.clear(); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 0eb452d29736..164c8b013c84 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1615,6 +1615,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final int count = tasksToReparent.size(); for (int i = 0; i < count; ++i) { final Task task = tasksToReparent.get(i); + final int prevWindowingMode = task.getWindowingMode(); if (syncId >= 0) { addToSyncSet(syncId, task); } @@ -1628,6 +1629,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, false /*moveParents*/, "processChildrenTaskReparentHierarchyOp"); } + // Trim the compatible Recent task (if any) after the Task is reparented and now has + // a different windowing mode, in order to prevent redundant Recent tasks after + // reparenting. + if (prevWindowingMode != task.getWindowingMode()) { + mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task); + } } if (transition != null) transition.collect(newParent); 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/permission/OWNERS b/services/permission/OWNERS index 6c6c9fc10d3b..e464038e68d9 100644 --- a/services/permission/OWNERS +++ b/services/permission/OWNERS @@ -1,4 +1,5 @@ -ashfall@google.com +#Bug component: 137825 + joecastro@google.com ntmyren@google.com zhanghai@google.com diff --git a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS index 999ea0e62a0a..2fe78c5a7092 100644 --- a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS +++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS @@ -1 +1,2 @@ +#Bug component: 137825 include /core/java/android/permission/OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS b/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS index 999ea0e62a0a..2fe78c5a7092 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS @@ -1 +1,2 @@ +#Bug component: 137825 include /core/java/android/permission/OWNERS 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/appop/OWNERS b/services/tests/servicestests/src/com/android/server/appop/OWNERS index 999ea0e62a0a..2fe78c5a7092 100644 --- a/services/tests/servicestests/src/com/android/server/appop/OWNERS +++ b/services/tests/servicestests/src/com/android/server/appop/OWNERS @@ -1 +1,2 @@ +#Bug component: 137825 include /core/java/android/permission/OWNERS 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/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml index 2a15c15fce41..a14ea5598758 100644 --- a/services/tests/vibrator/AndroidManifest.xml +++ b/services/tests/vibrator/AndroidManifest.xml @@ -31,8 +31,7 @@ <!-- Required to set always-on vibrations --> <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" /> - <application android:debuggable="true" - android:testOnly="true"> + <application android:debuggable="true"> <uses-library android:name="android.test.mock" android:required="true" /> <uses-library android:name="android.test.runner" /> </application> diff --git a/services/tests/vibrator/AndroidTest.xml b/services/tests/vibrator/AndroidTest.xml deleted file mode 100644 index d5ee3afaedb6..000000000000 --- a/services/tests/vibrator/AndroidTest.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<configuration description="Runs Frameworks Vibrator Services Tests."> - <option name="test-suite-tag" value="apct" /> - <option name="test-suite-tag" value="apct-instrumentation" /> - - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="install-arg" value="-t" /> - <option name="test-file-name" value="FrameworksVibratorServicesTests.apk" /> - </target_preparer> - - <option name="test-tag" value="FrameworksVibratorServicesTests" /> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.framework.services.tests.vibrator" /> - <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> - <option name="hidden-api-checks" value="false" /> - </test> -</configuration> diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING index f0a7e470f8fd..22b72fa4ff9e 100644 --- a/services/tests/vibrator/TEST_MAPPING +++ b/services/tests/vibrator/TEST_MAPPING @@ -1,21 +1,7 @@ { - "presubmit": [ + "imports": [ { - "name": "FrameworksVibratorServicesTests", - "options": [ - {"exclude-annotation": "android.platform.test.annotations.LargeTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] - } - ], - "postsubmit": [ - { - "name": "FrameworksVibratorServicesTests", - "options": [ - {"exclude-annotation": "org.junit.Ignore"} - ] + "path": "frameworks/base/services/core/java/com/android/server/vibrator" } ] } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 5c3102d870d0..65e77dcd4ca9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -182,12 +182,12 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @Test public void testLaunchState() { - final ToIntFunction<Boolean> launchTemplate = doRelaunch -> { + final ToIntFunction<Runnable> launchTemplate = action -> { clearInvocations(mLaunchObserver); onActivityLaunched(mTopActivity); notifyTransitionStarting(mTopActivity); - if (doRelaunch) { - mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity); + if (action != null) { + action.run(); } final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity); @@ -199,21 +199,27 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Assume that the process is started (ActivityBuilder has mocked the returned value of // ATMS#getProcessController) but the activity has not attached process. mTopActivity.app = null; - assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_WARM); mTopActivity.app = app; mNewActivityCreated = false; - assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_HOT); - assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */)) + assertWithMessage("Relaunch").that(launchTemplate.applyAsInt( + () -> mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity))) .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH); + assertWithMessage("Cold launch by restart").that(launchTemplate.applyAsInt( + () -> mActivityMetricsLogger.notifyBindApplication( + mTopActivity.info.applicationInfo))) + .isEqualTo(WaitResult.LAUNCH_STATE_COLD); + mTopActivity.app = null; mNewActivityCreated = true; doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid); - assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_COLD); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index f23e56df2580..7cb58022c0e7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1303,6 +1303,26 @@ public class RecentTasksTest extends WindowTestsBase { assertTrue(info.supportsMultiWindow); } + @Test + public void testRemoveCompatibleRecentTask() { + final Task task1 = createTaskBuilder(".Task").setWindowingMode( + WINDOWING_MODE_FULLSCREEN).build(); + mRecentTasks.add(task1); + final Task task2 = createTaskBuilder(".Task").setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW).build(); + mRecentTasks.add(task2); + assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, + true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size()); + + // Set windowing mode and ensure the same fullscreen task that created earlier is removed. + task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mRecentTasks.removeCompatibleRecentTask(task2); + assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, + true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size()); + assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, + true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId); + } + private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) { HardwareBuffer buffer = null; if (bufferSize != null) { 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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 3908947804cd..d5afe3b2f078 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -27,6 +27,11 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; @@ -91,9 +96,12 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; @@ -2255,6 +2263,169 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() { + final int displayWidth = 1600; + final int displayHeight = 1400; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN); + } + + @Test + public void testUserOverrideSplitScreenAspectRatioForPortraitDisplay() { + final int displayWidth = 1400; + final int displayHeight = 1600; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN); + } + + @Test + public void testUserOverrideDisplaySizeAspectRatioForLandscapeDisplay() { + final int displayWidth = 1600; + final int displayHeight = 1400; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayWidth / displayHeight; + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE); + } + + @Test + public void testUserOverrideDisplaySizeAspectRatioForPortraitDisplay() { + final int displayWidth = 1400; + final int displayHeight = 1600; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + + float expectedAspectRatio = 1f * displayHeight / displayWidth; + + testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE); + } + + @Test + public void testUserOverride32AspectRatioForPortraitDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600); + testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2); + } + + @Test + public void testUserOverride32AspectRatioForLandscapeDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2); + } + + @Test + public void testUserOverride43AspectRatioForPortraitDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600); + testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3); + } + + @Test + public void testUserOverride43AspectRatioForLandscapeDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3); + } + + @Test + public void testUserOverride169AspectRatioForPortraitDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1800, /* dh */ 1500); + testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9); + } + + @Test + public void testUserOverride169AspectRatioForLandscapeDisplay() { + setUpDisplaySizeWithApp(/* dw */ 1500, /* dh */ 1800); + testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) + public void testUserOverrideAspectRatioOverSystemOverride() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_PORTRAIT, + 3 / 2f, + USER_MIN_ASPECT_RATIO_3_2, + true); + } + + @Test + public void testUserOverrideAspectRatioNotEnabled() { + setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400); + + // App aspect ratio doesn't change + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_PORTRAIT, + 1f * 1600 / 1400, + USER_MIN_ASPECT_RATIO_3_2, + false); + } + + private void testUserOverrideAspectRatio(float expectedAspectRatio, + @PackageManager.UserMinAspectRatio int aspectRatio) { + testUserOverrideAspectRatio(true, + SCREEN_ORIENTATION_PORTRAIT, + expectedAspectRatio, + aspectRatio, + true); + + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_PORTRAIT, + expectedAspectRatio, + aspectRatio, + true); + + testUserOverrideAspectRatio(true, + SCREEN_ORIENTATION_LANDSCAPE, + expectedAspectRatio, + aspectRatio, + true); + + testUserOverrideAspectRatio(false, + SCREEN_ORIENTATION_LANDSCAPE, + expectedAspectRatio, + aspectRatio, + true); + } + + private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation, + float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio, + boolean enabled) { + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + activity.mWmService.mLetterboxConfiguration + .setUserAppAspectRatioSettingsOverrideEnabled(enabled); + // Set user aspect ratio override + final IPackageManager pm = mAtm.getPackageManager(); + try { + doReturn(aspectRatio).when(pm) + .getUserMinAspectRatio(activity.packageName, activity.mUserId); + } catch (RemoteException ignored) { + } + + prepareLimitedBounds(activity, screenOrientation, isUnresizable); + + final Rect afterBounds = activity.getBounds(); + final int width = afterBounds.width(); + final int height = afterBounds.height(); + final float afterAspectRatio = + (float) Math.max(width, height) / (float) Math.min(width, height); + + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + } + + @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN}) public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() { 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/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt new file mode 100644 index 000000000000..0417f9dbb4bf --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -0,0 +1,188 @@ +/* + * 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.server.wm.flicker.activityembedding + +import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launching a secondary Activity into Picture-In-Picture mode. + * + * Setup: Start from a split A|B. + * Transition: B enters PIP, observe the window shrink to the bottom right corner on screen. + * + * To run this test: `atest FlickerTests:SecondaryActivityEnterPipTest` + * + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SecondaryActivityEnterPipTest (flicker: LegacyFlickerTest) : + ActivityEmbeddingTestBase(flicker) { + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + testApp.launchViaIntent(wmHelper) + testApp.launchSecondaryActivity(wmHelper) + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds + ?: error("Can't get display bounds") + } + transitions { + testApp.secondaryActivityEnterPip(wmHelper) + } + teardown { + tapl.goHome() + testApp.exit(wmHelper) + } + } + + /** + * Main and secondary activity start from a split each taking half of the screen. + */ + @Presubmit + @Test + fun layersStartFromEqualSplit() { + flicker.assertLayersStart { + val leftLayerRegion = + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val rightLayerRegion = + visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + // Compare dimensions of two splits, given we're using default split attributes, + // both activities take up the same visible size on the display. + check { "height" } + .that(leftLayerRegion.region.height).isEqual(rightLayerRegion.region.height) + check { "width" } + .that(leftLayerRegion.region.width).isEqual(rightLayerRegion.region.width) + leftLayerRegion.notOverlaps(rightLayerRegion.region) + leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds) + } + flicker.assertLayersEnd { + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .coversExactly(startDisplayBounds) + } + } + + /** + * Main Activity is visible throughout the transition and becomes fullscreen. + */ + @Presubmit + @Test + fun mainActivityWindowBecomesFullScreen() { + flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } + flicker.assertWmEnd { + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .coversExactly(startDisplayBounds) + } + } + + /** + * Main Activity is visible throughout the transition and becomes fullscreen. + */ + @Presubmit + @Test + fun mainActivityLayerBecomesFullScreen() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .then() + .isVisible(TRANSITION_SNAPSHOT) + .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + } + flicker.assertLayersEnd { + visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .coversExactly(startDisplayBounds) + } + } + + /** + * Secondary Activity is visible throughout the transition and shrinks to the bottom right + * corner. + */ + @Presubmit + @Test + fun secondaryWindowShrinks() { + flicker.assertWm { + isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + } + flicker.assertWmEnd { + val pipWindowRegion = + visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + check{"height"} + .that(pipWindowRegion.region.height) + .isLower(startDisplayBounds.height / 2) + check{"width"} + .that(pipWindowRegion.region.width).isLower(startDisplayBounds.width) + } + } + + /** + * During the transition Secondary Activity shrinks to the bottom right corner. + */ + @Presubmit + @Test + fun secondaryLayerShrinks() { + flicker.assertLayers { + val pipLayerList = layers { + ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible + } + pipLayerList.zipWithNext { previous, current -> + // TODO(b/290987990): Add checks for visibleRegion. + current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3) + current.screenBounds.notBiggerThan(previous.screenBounds.region) + } + } + flicker.assertLayersEnd { + val pipRegion = visibleRegion( + ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + check { "height" } + .that(pipRegion.region.height) + .isLower(startDisplayBounds.height / 2) + check { "width" } + .that(pipRegion.region.width).isLower(startDisplayBounds.width) + } + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index a72ec1e678b3..2d3bc2d5eb15 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -113,6 +113,21 @@ constructor( .waitForAndVerify() } + fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) { + val pipButton = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")), + FIND_TIMEOUT + ) + require(pipButton != null) { "Can't find enter pip button on screen." } + pipButton.click() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withPipShown() + .waitForAndVerify() + } + /** * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch * a fullscreen window on top of the visible region. 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) diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 92c5f17a5b94..ff9799a1c710 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -224,6 +224,7 @@ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:supportsPictureInPicture="true" android:exported="false"/> <activity android:name=".ActivityEmbeddingThirdActivity" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml index 67314463161d..135140aa2377 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml @@ -35,4 +35,10 @@ android:onClick="launchThirdActivity" android:text="Launch a third activity" /> + <Button + android:id="@+id/secondary_enter_pip_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="Enter pip" /> + </LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java index dc21027bc99c..ee087ef9be2c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.content.Intent; +import android.app.PictureInPictureParams; import android.graphics.Color; import android.os.Bundle; import android.view.View; @@ -40,6 +41,16 @@ public class ActivityEmbeddingSecondaryActivity extends Activity { finish(); } }); + findViewById(R.id.secondary_enter_pip_button).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + PictureInPictureParams.Builder picInPicParamsBuilder = + new PictureInPictureParams.Builder(); + enterPictureInPictureMode(picInPicParamsBuilder.build()); + } + } + ); } public void launchThirdActivity(View view) { diff --git a/tests/permission/OWNERS b/tests/permission/OWNERS index 999ea0e62a0a..2fe78c5a7092 100644 --- a/tests/permission/OWNERS +++ b/tests/permission/OWNERS @@ -1 +1,2 @@ +#Bug component: 137825 include /core/java/android/permission/OWNERS |