diff options
276 files changed, 5006 insertions, 2123 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 cf26e12dc761..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(); } @@ -4235,6 +4236,7 @@ package android.window { field public final boolean isTrustedOverlay; field public final boolean isVisible; field @NonNull public final String name; + field @NonNull public final android.graphics.Matrix transform; field @NonNull public final android.os.IBinder windowToken; } 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/content/Intent.java b/core/java/android/content/Intent.java index c10062cfb89c..fe108a513ec5 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6546,6 +6546,14 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_REASON = "android.intent.extra.REASON"; /** + * Intent extra: Whether to show the wipe progress UI or to skip it. + * + * <p>Type: boolean + * @hide + */ + public static final String EXTRA_SHOW_WIPE_PROGRESS = "android.intent.extra.SHOW_WIPE_PROGRESS"; + + /** * {@hide} * This extra will be send together with {@link #ACTION_FACTORY_RESET} */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2948bd9b8868..bef023e1feee 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1432,7 +1432,6 @@ public abstract class PackageManager { INSTALL_ALLOW_DOWNGRADE, INSTALL_STAGED, INSTALL_REQUEST_UPDATE_OWNERSHIP, - INSTALL_DONT_EXTRACT_BASELINE_PROFILES, }) @Retention(RetentionPolicy.SOURCE) public @interface InstallFlags {} @@ -1647,13 +1646,6 @@ public abstract class PackageManager { */ public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26; - /** - * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that do not extract - * the baseline profiles when parsing the apk - * @hide - */ - public static final int INSTALL_DONT_EXTRACT_BASELINE_PROFILES = 1 << 27; - /** @hide */ @IntDef(flag = true, value = { DONT_KILL_APP, diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java index 3b53c25d1d8b..e75aa065d3d3 100644 --- a/core/java/android/content/pm/dex/DexMetadataHelper.java +++ b/core/java/android/content/pm/dex/DexMetadataHelper.java @@ -23,9 +23,6 @@ import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.JsonReader; @@ -36,7 +33,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.security.VerityUtils; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -48,7 +44,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; /** * Helper class used to compute and validate the location of dex metadata files. @@ -67,11 +62,6 @@ public class DexMetadataHelper { private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; - private static final String PROFILE_FILE_NAME = "primary.prof"; - private static final String PROFILE_METADATA_FILE_NAME = "primary.profm"; - private static final String BASELINE_PROFILE_SOURCE_RELATIVE_PATH = "dexopt/baseline.prof"; - private static final String BASELINE_PROFILE_METADATA_RELATIVE_PATH = "dexopt/baseline.profm"; - private DexMetadataHelper() {} /** Return true if the given file is a dex metadata file. */ @@ -323,76 +313,4 @@ public class DexMetadataHelper { } } - /** - * Extract the baseline profiles from the assets directory in the apk. Then create a - * ZIP archive with the (.dm) file extension (e.g. foo.dm from foo.apk) to include the - * baseline profiles and put the DexMetadata file in the same directory with the apk. - * - * @param assetManager The {@link AssetManager} to use. - * @param apkPath The path of the apk - * @return {@code true} if the extraction is successful. Otherwise, return {@code false}. - * - * @see #buildDexMetadataPathForApk(String) - */ - public static boolean extractBaselineProfilesToDexMetadataFileFromApk(AssetManager assetManager, - String apkPath) { - if (!ApkLiteParseUtils.isApkPath(apkPath)) { - if (DEBUG) { - Log.d(TAG, "It is not an apk file: " + apkPath); - } - return false; - } - - // get the name of the DexMetadata file from the path of the apk - final File dmFile = new File(buildDexMetadataPathForApk(apkPath)); - boolean success = false; - - // load profile and profile metadata from assets directory in the apk - try (InputStream profileIs = openStreamFromAssets(assetManager, - BASELINE_PROFILE_SOURCE_RELATIVE_PATH); - InputStream profileMetadataIs = openStreamFromAssets(assetManager, - BASELINE_PROFILE_METADATA_RELATIVE_PATH)) { - // Create the zip archive file and write the baseline profiles into it. - try (FileOutputStream fos = new FileOutputStream(dmFile)) { - try (ZipOutputStream zipOs = new ZipOutputStream(fos)) { - zipOs.putNextEntry(new ZipEntry(PROFILE_FILE_NAME)); - zipOs.write(profileIs.readAllBytes()); - zipOs.closeEntry(); - - zipOs.putNextEntry(new ZipEntry(PROFILE_METADATA_FILE_NAME)); - zipOs.write(profileMetadataIs.readAllBytes()); - zipOs.closeEntry(); - success = true; - } - } - } catch (IOException e) { - if (DEBUG) { - Log.e(TAG, "Extract baseline profiles from apk failed: " + e.getMessage()); - } - } finally { - if (!success) { - if (dmFile.exists()) { - dmFile.delete(); - } - } - } - return success; - } - - /** - * Loads an {@link AutoCloseInputStream} from assets with the path. - * - * @param assetManager The {@link AssetManager} to use. - * @param path The source file's relative path. - * @return An AutoCloseInputStream in case the file was successfully read. - * @throws IOException If anything goes wrong while opening or reading the file. - */ - private static AutoCloseInputStream openStreamFromAssets(AssetManager assetManager, String path) - throws IOException { - AssetFileDescriptor descriptor = assetManager.openFd(path); - // Based on the java doc of AssetFileDescriptor#createInputStream, it will always return - // an AutoCloseInputStream. It should be fine we cast it from FileInputStream to - // AutoCloseInputStream here. - return (AutoCloseInputStream) descriptor.createInputStream(); - } } diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java index d41a428355ea..e91e04ec9cd1 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java @@ -28,7 +28,6 @@ import com.android.internal.vibrator.persistence.VibrationEffectXmlParser; import com.android.internal.vibrator.persistence.XmlConstants; import com.android.internal.vibrator.persistence.XmlParserException; import com.android.internal.vibrator.persistence.XmlReader; -import com.android.internal.vibrator.persistence.XmlSerializedVibration; import com.android.modules.utils.TypedXmlPullParser; import org.xmlpull.v1.XmlPullParser; @@ -178,25 +177,62 @@ public final class VibrationXmlParser { // Ensure XML starts with expected root tag. XmlReader.readDocumentStartTag(parser, XmlConstants.TAG_VIBRATION); - int parserFlags = 0; - if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) { - parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS; - } - // Parse root tag as a vibration effect. - XmlSerializedVibration<VibrationEffect> serializedVibration = - VibrationEffectXmlParser.parseTag(parser, parserFlags); + VibrationEffect effect = parseTag(parser, flags); // Ensure XML ends after root tag is consumed. XmlReader.readDocumentEndTag(parser); - return serializedVibration.deserialize(); - } catch (XmlParserException e) { + return effect; + } catch (XmlParserException | VibrationXmlParserException e) { Slog.w(TAG, "Error parsing vibration XML", e); return null; } } + /** + * Parses XML content from given open {@link TypedXmlPullParser} into a {@link VibrationEffect}. + * + * <p>The provided parser should be pointing to a start of a valid vibration XML (i.e. to a + * start <vibration> tag). No other parser position, including start of document, is considered + * valid. + * + * <p>This method parses as long as it reads a valid vibration XML, and until an end vibration + * tag. After a successful parsing, the parser will point to the end vibration tag (i.e. to a + * </vibration> tag). + * + * @throws IOException error parsing from given {@link TypedXmlPullParser}. + * @throws VibrationXmlParserException if the XML tag cannot be parsed into a + * {@link VibrationEffect}. The given {@code parser} might be pointing to a child XML tag + * that caused the parser failure. + * + * @hide + */ + @NonNull + public static VibrationEffect parseTag(@NonNull TypedXmlPullParser parser, @Flags int flags) + throws IOException, VibrationXmlParserException { + int parserFlags = 0; + if ((flags & VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS) != 0) { + parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS; + } + try { + return VibrationEffectXmlParser.parseTag(parser, parserFlags).deserialize(); + } catch (XmlParserException e) { + throw new VibrationXmlParserException("Error parsing vibration effect.", e); + } + } + + /** + * Represents an error while parsing a vibration XML input. + * + * @hide + */ + public static final class VibrationXmlParserException extends Exception { + private VibrationXmlParserException(String message, Throwable cause) { + super(message, cause); + } + } + private VibrationXmlParser() { } } 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/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java index bdbce134801d..be88e53ad507 100644 --- a/core/java/android/window/WindowInfosListenerForTest.java +++ b/core/java/android/window/WindowInfosListenerForTest.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.TestApi; +import android.graphics.Matrix; import android.graphics.Rect; import android.os.IBinder; import android.os.InputConfig; @@ -78,14 +79,21 @@ public class WindowInfosListenerForTest { */ public final boolean isVisible; + /** + * Return the transform to get the bounds from display space into window space. + */ + @NonNull + public final Matrix transform; + WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId, - @NonNull Rect bounds, int inputConfig) { + @NonNull Rect bounds, int inputConfig, @NonNull Matrix transform) { this.windowToken = windowToken; this.name = name; this.displayId = displayId; this.bounds = bounds; this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0; this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0; + this.transform = transform; } @Override @@ -94,7 +102,8 @@ public class WindowInfosListenerForTest { + ", frame=" + bounds + ", isVisible=" + isVisible + ", isTrustedOverlay=" + isTrustedOverlay - + ", token=" + windowToken; + + ", token=" + windowToken + + ", transform=" + transform; } } @@ -155,7 +164,7 @@ public class WindowInfosListenerForTest { var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight, handle.frameBottom); windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, handle.displayId, - bounds, handle.inputConfig)); + bounds, handle.inputConfig, handle.transform)); } return windowInfos; } 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/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java index c0fdf9f2d527..b31af89e345b 100644 --- a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +++ b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java @@ -29,10 +29,14 @@ import static org.junit.Assert.assertThrows; import android.os.VibrationEffect; import android.os.vibrator.PrebakedSegment; import android.platform.test.annotations.Presubmit; +import android.util.Xml; + +import com.android.modules.utils.TypedXmlPullParser; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.xmlpull.v1.XmlPullParser; import java.io.IOException; import java.io.StringReader; @@ -65,6 +69,61 @@ public class VibrationEffectXmlSerializationTest { } @Test + public void testParseTag_succeedAndParserPointsToEndVibrationTag() throws Exception { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_TICK, 0.2497f) + .compose(); + String xml = "<vibration>" + + "<primitive-effect name=\"click\"/>" + + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>" + + "</vibration>"; + VibrationEffect effect2 = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) + .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) + .compose(); + String xml2 = "<vibration>" + + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>" + + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>" + + "</vibration>"; + + TypedXmlPullParser parser = createXmlPullParser(xml); + assertParseTagSucceeds(parser, effect); + parser.next(); + assertEndOfDocument(parser); + + // Test no-issues when an end-tag follows the vibration XML. + // To test this, starting with the corresponding "start-tag" is necessary. + parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>"); + // Move the parser once to point to the "<vibration> tag. + parser.next(); + assertParseTagSucceeds(parser, effect); + parser.next(); + assertEndTag(parser, "next-tag"); + + parser = createXmlPullParser(xml + "<next-tag>"); + assertParseTagSucceeds(parser, effect); + parser.next(); + assertStartTag(parser, "next-tag"); + + parser = createXmlPullParser(xml + xml2); + assertParseTagSucceeds(parser, effect); + parser.next(); + assertParseTagSucceeds(parser, effect2); + parser.next(); + assertEndOfDocument(parser); + } + + @Test + public void testParseTag_badXml_throwsException() throws Exception { + assertParseTagFails( + "<vibration>random text<primitive-effect name=\"click\"/></vibration>"); + assertParseTagFails("<bad-tag><primitive-effect name=\"click\"/></vibration>"); + assertParseTagFails("<primitive-effect name=\"click\"/></vibration>"); + assertParseTagFails("<vibration><primitive-effect name=\"click\"/>"); + } + + @Test public void testPrimitives_allSucceed() throws IOException { VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(PRIMITIVE_CLICK) @@ -172,6 +231,41 @@ public class VibrationEffectXmlSerializationTest { assertThat(parse(xml, /* flags= */ 0)).isEqualTo(effect); } + private TypedXmlPullParser createXmlPullParser(String xml) throws Exception { + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new StringReader(xml)); + parser.next(); // read START_DOCUMENT + return parser; + } + + /** + * Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser + * points to the end "vibration" tag. + */ + private void assertParseTagSucceeds( + TypedXmlPullParser parser, VibrationEffect effect) throws Exception { + assertThat(parseTag(parser)).isEqualTo(effect); + + assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG); + assertThat(parser.getName()).isEqualTo("vibration"); + } + + private void assertEndTag(TypedXmlPullParser parser, String expectedTagName) throws Exception { + assertThat(parser.getName()).isEqualTo(expectedTagName); + assertThat(parser.getEventType()).isEqualTo(parser.END_TAG); + } + + private void assertStartTag(TypedXmlPullParser parser, String expectedTagName) + throws Exception { + assertThat(parser.getName()).isEqualTo(expectedTagName); + assertThat(parser.getEventType()).isEqualTo(parser.START_TAG); + } + + private void assertEndOfDocument(TypedXmlPullParser parser) throws Exception { + assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT); + } + private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect) throws IOException { assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect); @@ -183,6 +277,12 @@ public class VibrationEffectXmlSerializationTest { () -> serialize(effect, /* flags= */ 0)); } + private void assertParseTagFails(String xml) { + assertThrows("Expected parsing to fail for " + xml, + VibrationXmlParser.VibrationXmlParserException.class, + () -> parseTag(createXmlPullParser(xml))); + } + private void assertPublicApisSerializerSucceeds(VibrationEffect effect, String... expectedSegments) throws IOException { assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments); @@ -214,6 +314,11 @@ public class VibrationEffectXmlSerializationTest { return VibrationXmlParser.parse(new StringReader(xml), flags); } + private static VibrationEffect parseTag(TypedXmlPullParser parser) + throws IOException, VibrationXmlParser.VibrationXmlParserException { + return VibrationXmlParser.parseTag(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS); + } + private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags) throws IOException { StringWriter writer = new StringWriter(); 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/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt index 0f9579d58929..69c8ecd5644d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.appcompat import android.content.Context -import android.system.helpers.CommandsHelper import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTestData @@ -29,15 +28,18 @@ import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.layerKeepVisible -import org.junit.After + import org.junit.Assume import org.junit.Before +import org.junit.Rule abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val context: Context = instrumentation.context protected val letterboxApp = LetterboxAppHelper(instrumentation) - lateinit var cmdHelper: CommandsHelper - private lateinit var letterboxStyle: HashMap<String, String> + + @JvmField + @Rule + val letterboxRule: LetterboxRule = LetterboxRule() /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -52,50 +54,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { @Before fun before() { - cmdHelper = CommandsHelper.getInstance(instrumentation) - Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest()) - letterboxStyle = mapLetterboxStyle() - resetLetterboxStyle() - setLetterboxEducationEnabled(false) - } - - @After - fun after() { - resetLetterboxStyle() - } - - private fun mapLetterboxStyle(): HashMap<String, String> { - val res = cmdHelper.executeShellCommand("wm get-letterbox-style") - val lines = res.lines() - val map = HashMap<String, String>() - for (line in lines) { - val keyValuePair = line.split(":") - if (keyValuePair.size == 2) { - val key = keyValuePair[0].trim() - map[key] = keyValuePair[1].trim() - } - } - return map - } - - private fun getLetterboxStyle(): HashMap<String, String> { - if (!::letterboxStyle.isInitialized) { - letterboxStyle = mapLetterboxStyle() - } - return letterboxStyle - } - - private fun resetLetterboxStyle() { - cmdHelper.executeShellCommand("wm reset-letterbox-style") - } - - private fun setLetterboxEducationEnabled(enabled: Boolean) { - cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled") - } - - private fun isIgnoreOrientationRequest(): Boolean { - val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request") - return res != null && res.contains("true") + Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest) } fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation) @@ -115,7 +74,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */ private fun assumeLetterboxRoundedCornersEnabled() { - Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0") + Assume.assumeTrue(letterboxRule.hasCornerRadius) } fun assertLetterboxAppVisibleAtStartAndEnd() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt new file mode 100644 index 000000000000..5a1136f97c6f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt @@ -0,0 +1,108 @@ +/* + * 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.wm.shell.flicker.appcompat + +import android.app.Instrumentation +import android.system.helpers.CommandsHelper +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * JUnit Rule to handle letterboxStyles and states + */ +class LetterboxRule( + private val withLetterboxEducationEnabled: Boolean = false, + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), + private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation) +) : TestRule { + + private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)} + private lateinit var _letterboxStyle: MutableMap<String, String> + + val letterboxStyle: Map<String, String> + get() { + if (!::_letterboxStyle.isInitialized) { + _letterboxStyle = mapLetterboxStyle() + } + return _letterboxStyle + } + + val cornerRadius: Int? + get() = asInt(letterboxStyle["Corner radius"]) + + val hasCornerRadius: Boolean + get() { + val radius = cornerRadius + return radius != null && radius > 0 + } + + val isIgnoreOrientationRequest: Boolean + get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false + + override fun apply(base: Statement?, description: Description?): Statement { + resetLetterboxStyle() + _letterboxStyle = mapLetterboxStyle() + val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled") + var hasLetterboxEducationStateChanged = false + if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) { + hasLetterboxEducationStateChanged = true + execAdb("wm set-letterbox-style --isEducationEnabled " + + withLetterboxEducationEnabled) + } + return try { + object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + base!!.evaluate() + } + } + } finally { + if (hasLetterboxEducationStateChanged) { + execAdb("wm set-letterbox-style --isEducationEnabled " + + isLetterboxEducationEnabled + ) + } + resetLetterboxStyle() + } + } + + private fun mapLetterboxStyle(): HashMap<String, String> { + val res = execAdb("wm get-letterbox-style") + val lines = res.lines() + val map = HashMap<String, String>() + for (line in lines) { + val keyValuePair = line.split(":") + if (keyValuePair.size == 2) { + val key = keyValuePair[0].trim() + map[key] = keyValuePair[1].trim() + } + } + return map + } + + private fun resetLetterboxStyle() { + execAdb("wm reset-letterbox-style") + } + + private fun asInt(str: String?): Int? = try { + str?.toInt() + } catch (e: NumberFormatException) { + null + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt index a7bd2584ba23..67d5718e6c1f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test launching app in size compat mode. * - * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt new file mode 100644 index 000000000000..e6ca261a317f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt @@ -0,0 +1,128 @@ +/* + * 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.wm.shell.flicker.appcompat + +import android.platform.test.annotations.Postsubmit +import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.traces.component.ComponentNameMatcher +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 org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching app in size compat mode. + * + * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest` + * + * Actions: + * ``` + * Launch a letteboxed app and then a transparent activity from it. We test the bounds + * are the same. + * ``` + * + * Notes: + * ``` + * Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [BaseTest] + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + setup { + letterboxTranslucentLauncherApp.launchViaIntent(wmHelper) + } + transitions { + waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found") + } + teardown { + letterboxTranslucentApp.exit(wmHelper) + letterboxTranslucentLauncherApp.exit(wmHelper) + } + } + + /** + * Checks the transparent activity is launched on top of the opaque one + */ + @Postsubmit + @Test + fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() { + flicker.assertWm { + this.isAppWindowOnTop(letterboxTranslucentLauncherApp) + .then() + .isAppWindowOnTop(letterboxTranslucentApp) + } + } + + /** + * Checks that the activity is letterboxed + */ + @Postsubmit + @Test + fun translucentActivityIsLetterboxed() { + flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) } + } + + /** + * Checks that the translucent activity inherits bounds from the opaque one. + */ + @Postsubmit + @Test + fun translucentActivityInheritsBoundsFromOpaqueActivity() { + flicker.assertLayersEnd { + this.visibleRegion(letterboxTranslucentApp) + .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region) + } + } + + /** + * Checks that the translucent activity has rounded corners + */ + @Postsubmit + @Test + fun translucentActivityHasRoundedCorners() { + flicker.assertLayersEnd { + this.hasRoundedCorners(letterboxTranslucentApp) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.rotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory + .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90)) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt index e875aae431a1..68fa8d2fc2e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt @@ -32,7 +32,9 @@ import org.junit.runners.Parameterized /** * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right. * - * To run this test: `atest WMShellFlickerTests:RepositionFixedPortraitAppTest` Actions: + * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest` + * + * Actions: * * ``` * Launch a fixed portrait app in landscape to letterbox app diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt index a18a144b4bf1..fcb6931af9a2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test restarting app in size compat mode. * - * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt new file mode 100644 index 000000000000..ea0392cee95a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt @@ -0,0 +1,63 @@ +/* + * 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.wm.shell.flicker.appcompat + +import android.content.Context +import android.tools.device.flicker.legacy.FlickerTestData +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.toFlickerComponent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.LetterboxAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.BaseTest +import org.junit.Assume +import org.junit.Before +import org.junit.Rule + +abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { + protected val context: Context = instrumentation.context + protected val letterboxTranslucentLauncherApp = LetterboxAppHelper( + instrumentation, + launcherName = ActivityOptions.LaunchTransparentActivity.LABEL, + component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent() + ) + protected val letterboxTranslucentApp = LetterboxAppHelper( + instrumentation, + launcherName = ActivityOptions.TransparentActivity.LABEL, + component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent() + ) + + @JvmField + @Rule + val letterboxRule: LetterboxRule = LetterboxRule() + + @Before + fun before() { + Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest) + } + + protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? = + device.wait( + Until.findObject(By.text("Launch Transparent")), + FIND_TIMEOUT + ) + + protected fun FlickerTestData.goBack() = device.pressBack() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt index 92b62273d8cb..2494054c1fae 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit import org.junit.Test -@RequiresDevice -class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) { +class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt index 566adec75615..57943ec4c8fa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit import org.junit.Test -@RequiresDevice -class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) { +class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt index e6d56b5c94d3..6f0e202ae70f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider import org.junit.Test -@RequiresDevice -class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark : +class DismissSplitScreenByDividerGesturalNavLandscape : DismissSplitScreenByDivider(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt index 6752c58bd568..dac8fa24b289 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider import org.junit.Test -@RequiresDevice -class DismissSplitScreenByDividerGesturalNavPortraitBenchmark : +class DismissSplitScreenByDividerGesturalNavPortrait : DismissSplitScreenByDivider(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt index 7c9ab9939dd0..baecc161d227 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome import org.junit.Test -@RequiresDevice -class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark : +class DismissSplitScreenByGoHomeGesturalNavLandscape : DismissSplitScreenByGoHome(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt index 4b795713cb23..3063ea564b58 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome import org.junit.Test -@RequiresDevice -class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark : +class DismissSplitScreenByGoHomeGesturalNavPortrait : DismissSplitScreenByGoHome(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt index 71ef48bea686..41660ba68385 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize import org.junit.Test -@RequiresDevice -class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) { +class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt index 04950799732e..c41ffb786cd9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize import org.junit.Test -@RequiresDevice -class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) { +class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt index c78729c6dc92..afde55bf519f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark : +class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt index 30bce2f657b1..3765fc42d2c9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark : +class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt index b33ea7c89158..1e128fd3e47f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark : +class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt index 07a86a57117b..7767872cb7fd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark : +class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt index 9a1d12787b9d..4ca4bd1d6d8b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark : +class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt index 266e268a3537..2d9d258ecc54 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark : +class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt index 83fc30bceb7b..c9282acc5a2f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark : +class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt index b2f19299c7f0..68c6d6cccec1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test -@RequiresDevice -class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark : +class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt index dae92dddbfec..304529ec83fb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview import org.junit.Test -@RequiresDevice -class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark : +class EnterSplitScreenFromOverviewGesturalNavLandscape : EnterSplitScreenFromOverview(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt index 732047ba38ad..53a6b449df2b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview import org.junit.Test -@RequiresDevice -class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark : +class EnterSplitScreenFromOverviewGesturalNavPortrait : EnterSplitScreenFromOverview(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt index 1de7efd7970a..a4678301be12 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test -@RequiresDevice -class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark : +class SwitchAppByDoubleTapDividerGesturalNavLandscape : SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt index 1a046aa5b09e..15242330a554 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test -@RequiresDevice -class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark : +class SwitchAppByDoubleTapDividerGesturalNavPortrait : SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt index 6e88f0eddee8..0389659457e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test -@RequiresDevice -class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark : +class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt index d26a29c80583..7fadf184d74b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test -@RequiresDevice -class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark : +class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt index 4a552b0aed6a..148cc522e64d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome import org.junit.Test -@RequiresDevice -class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark : +class SwitchBackToSplitFromHomeGesturalNavLandscape : SwitchBackToSplitFromHome(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt index b7376eaea66d..0641fb03527c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome import org.junit.Test -@RequiresDevice -class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark : +class SwitchBackToSplitFromHomeGesturalNavPortrait : SwitchBackToSplitFromHome(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt index b2d05e4a2632..741f871e2c5f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent import org.junit.Test -@RequiresDevice -class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark : +class SwitchBackToSplitFromRecentGesturalNavLandscape : SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt index 6de31b1315e4..778e2d6a048a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent import org.junit.Test -@RequiresDevice -class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark : +class SwitchBackToSplitFromRecentGesturalNavPortrait : SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt index b074f2c161c9..dcdca70e0f11 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -14,18 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs import org.junit.Test -@RequiresDevice -class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark : - SwitchBetweenSplitPairs(Rotation.ROTATION_0) { +class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt index aab18a6d27b9..3c69311a7028 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -14,18 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs import org.junit.Test -@RequiresDevice -class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark : - SwitchBetweenSplitPairs(Rotation.ROTATION_90) { +class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt index 840401c23a91..c6566f5bec80 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -14,19 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner -@RequiresDevice @RunWith(BlockJUnit4ClassRunner::class) -class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() { +class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt index c402aa4444d8..bb1a5025c5dc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -14,19 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.benchmark +package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner -@RequiresDevice @RunWith(BlockJUnit4ClassRunner::class) -class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() { +class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt index d44d1779a3f6..9f9d4bb1a2a4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt @@ -47,6 +47,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { fun setup() { tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) + tapl.workspace.switchToOverview().dismissAllTasks() SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index f3f32eb6897c..e227999c2432 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -14,30 +14,32 @@ * limitations under the License. */ -#include "tests/common/LeakChecker.h" -#include "tests/common/TestScene.h" - -#include "Properties.h" -#include "hwui/Typeface.h" -#include "HardwareBitmapUploader.h" -#include "renderthread/RenderProxy.h" - +#include <android-base/parsebool.h> #include <benchmark/benchmark.h> +#include <errno.h> +#include <fcntl.h> #include <fnmatch.h> #include <getopt.h> #include <pthread.h> #include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> #include <unistd.h> + +#include <regex> #include <string> #include <unordered_map> #include <vector> -#include <errno.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> +#include "HardwareBitmapUploader.h" +#include "Properties.h" +#include "hwui/Typeface.h" +#include "renderthread/RenderProxy.h" +#include "tests/common/LeakChecker.h" +#include "tests/common/TestScene.h" using namespace android; +using namespace android::base; using namespace android::uirenderer; using namespace android::uirenderer::test; @@ -69,6 +71,9 @@ OPTIONS: --onscreen Render tests on device screen. By default tests are offscreen rendered --benchmark_format Set output format. Possible values are tabular, json, csv + --benchmark_list_tests Lists the tests that would run but does not run them + --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test + that doesn't match the given regex is run --renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk --skip-leak-check Skips the memory leak check --report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run @@ -140,6 +145,9 @@ static bool setBenchmarkFormat(const char* format) { if (!strcmp(format, "tabular")) { gBenchmarkReporter.reset(new benchmark::ConsoleReporter()); } else if (!strcmp(format, "json")) { + // We cannot print the leak check if outputing to JSON as that will break + // JSON parsers since it's not JSON-formatted + gRunLeakCheck = false; gBenchmarkReporter.reset(new benchmark::JSONReporter()); } else { fprintf(stderr, "Unknown format '%s'\n", format); @@ -160,6 +168,24 @@ static bool setRenderer(const char* renderer) { return true; } +static void addTestsThatMatchFilter(std::string spec) { + if (spec.empty() || spec == "all") { + spec = "."; // Regexp that matches all benchmarks + } + bool isNegativeFilter = false; + if (spec[0] == '-') { + spec.replace(0, 1, ""); + isNegativeFilter = true; + } + std::regex re(spec, std::regex_constants::extended); + for (auto& iter : TestScene::testMap()) { + if ((isNegativeFilter && !std::regex_search(iter.first, re)) || + (!isNegativeFilter && std::regex_search(iter.first, re))) { + gRunTests.push_back(iter.second); + } + } +} + // For options that only exist in long-form. Anything in the // 0-255 range is reserved for short options (which just use their ASCII value) namespace LongOpts { @@ -170,6 +196,8 @@ enum { ReportFrametime, CpuSet, BenchmarkFormat, + BenchmarkListTests, + BenchmarkFilter, Onscreen, Offscreen, Renderer, @@ -179,14 +207,16 @@ enum { } static const struct option LONG_OPTIONS[] = { - {"frames", required_argument, nullptr, 'f'}, - {"repeat", required_argument, nullptr, 'r'}, + {"count", required_argument, nullptr, 'c'}, + {"runs", required_argument, nullptr, 'r'}, {"help", no_argument, nullptr, 'h'}, {"list", no_argument, nullptr, LongOpts::List}, {"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu}, {"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime}, {"cpuset", required_argument, nullptr, LongOpts::CpuSet}, {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat}, + {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests}, + {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter}, {"onscreen", no_argument, nullptr, LongOpts::Onscreen}, {"offscreen", no_argument, nullptr, LongOpts::Offscreen}, {"renderer", required_argument, nullptr, LongOpts::Renderer}, @@ -197,8 +227,12 @@ static const struct option LONG_OPTIONS[] = { static const char* SHORT_OPTIONS = "c:r:h"; void parseOptions(int argc, char* argv[]) { + benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown"; + int c; bool error = false; + bool listTestsOnly = false; + bool testsAreFiltered = false; opterr = 0; while (true) { @@ -272,6 +306,21 @@ void parseOptions(int argc, char* argv[]) { } break; + case LongOpts::BenchmarkListTests: + if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) { + listTestsOnly = true; + } + break; + + case LongOpts::BenchmarkFilter: + if (!optarg) { + error = true; + break; + } + addTestsThatMatchFilter(optarg); + testsAreFiltered = true; + break; + case LongOpts::Renderer: if (!optarg) { error = true; @@ -346,11 +395,18 @@ void parseOptions(int argc, char* argv[]) { } } } while (optind < argc); - } else { + } else if (gRunTests.empty() && !testsAreFiltered) { for (auto& iter : TestScene::testMap()) { gRunTests.push_back(iter.second); } } + + if (listTestsOnly) { + for (auto& iter : gRunTests) { + std::cout << iter.name << std::endl; + } + exit(EXIT_SUCCESS); + } } int main(int argc, char* argv[]) { diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 13936603e604..91fa873078fc 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -450,27 +450,27 @@ public final class MediaRoute2Info implements Parcelable { public static final String FEATURE_REMOTE_GROUP_PLAYBACK = "android.media.route.feature.REMOTE_GROUP_PLAYBACK"; - final String mId; - final CharSequence mName; - final List<String> mFeatures; + private final String mId; + private final CharSequence mName; + private final List<String> mFeatures; @Type - final int mType; - final boolean mIsSystem; - final Uri mIconUri; - final CharSequence mDescription; + private final int mType; + private final boolean mIsSystem; + private final Uri mIconUri; + private final CharSequence mDescription; @ConnectionState - final int mConnectionState; - final String mClientPackageName; - final String mPackageName; - final int mVolumeHandling; - final int mVolumeMax; - final int mVolume; - final String mAddress; - final Set<String> mDeduplicationIds; - final Bundle mExtras; - final String mProviderId; - final boolean mIsVisibilityRestricted; - final Set<String> mAllowedPackages; + private final int mConnectionState; + private final String mClientPackageName; + private final String mPackageName; + private final int mVolumeHandling; + private final int mVolumeMax; + private final int mVolume; + private final String mAddress; + private final Set<String> mDeduplicationIds; + private final Bundle mExtras; + private final String mProviderId; + private final boolean mIsVisibilityRestricted; + private final Set<String> mAllowedPackages; MediaRoute2Info(@NonNull Builder builder) { mId = builder.mId; 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/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index a0ff216875a2..ddb92b19b0cf 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -27,14 +27,17 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material3.Divider +import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.android.settingslib.development.DevelopmentSettingsEnabler import com.android.settingslib.spa.framework.compose.rememberDrawablePainter import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.widget.ui.SettingsBody @@ -80,11 +83,22 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { } @Composable - fun FooterAppVersion() { + fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) { if (packageInfo.versionName == null) return - Divider() - Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) { + HorizontalDivider() + Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) { SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped)) + if (showPackageName) { + SettingsBody(packageInfo.packageName) + } + } + } + + @Composable + private fun rememberIsDevelopmentSettingsEnabled(): Boolean { + val context = LocalContext.current + return remember { + DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt index e84668735fab..155905be4261 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -19,26 +19,26 @@ package com.android.settingslib.spaprivileged.template.app import androidx.compose.runtime.Composable import androidx.compose.runtime.State import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference import com.android.settingslib.spaprivileged.model.app.AppRecord @Composable fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem( - onClick: () -> Unit, checked: State<Boolean?>, changeable: State<Boolean>, onCheckedChange: ((newChecked: Boolean) -> Unit)?, ) { - TwoTargetSwitchPreference( + SwitchPreference( model = object : SwitchPreferenceModel { override val title = label override val summary = this@AppListSwitchItem.summary + override val icon = @Composable { + AppIcon(record.app, SettingsDimension.appIconItemSize) + } override val checked = checked override val changeable = changeable override val onCheckedChange = onCheckedChange }, - icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) }, - onClick = onClick, ) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt new file mode 100644 index 000000000000..99d38401829e --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt @@ -0,0 +1,44 @@ +/* + * 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.settingslib.spaprivileged.template.app + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference +import com.android.settingslib.spaprivileged.model.app.AppRecord + +@Composable +fun <T : AppRecord> AppListItemModel<T>.AppListTwoTargetSwitchItem( + onClick: () -> Unit, + checked: State<Boolean?>, + changeable: State<Boolean>, + onCheckedChange: ((newChecked: Boolean) -> Unit)?, +) { + TwoTargetSwitchPreference( + model = object : SwitchPreferenceModel { + override val title = label + override val summary = this@AppListTwoTargetSwitchItem.summary + override val checked = checked + override val changeable = changeable + override val onCheckedChange = onCheckedChange + }, + icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) }, + onClick = onClick, + ) +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt index bb56c10b28e3..6831e56596cc 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt @@ -93,6 +93,7 @@ class AppInfoTest { val packageInfo = PackageInfo().apply { applicationInfo = APP versionName = VERSION_NAME + packageName = PACKAGE_NAME } val appInfoProvider = AppInfoProvider(packageInfo) @@ -105,9 +106,45 @@ class AppInfoTest { composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed() } + @Test + fun footerAppVersion_developmentEnabled_packageNameIsDisplayed() { + val packageInfo = PackageInfo().apply { + applicationInfo = APP + versionName = VERSION_NAME + packageName = PACKAGE_NAME + } + val appInfoProvider = AppInfoProvider(packageInfo) + + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + appInfoProvider.FooterAppVersion(true) + } + } + composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed() + } + + + @Test + fun footerAppVersion_developmentDisabled_packageNameDoesNotExist() { + val packageInfo = PackageInfo().apply { + applicationInfo = APP + versionName = VERSION_NAME + packageName = PACKAGE_NAME + } + val appInfoProvider = AppInfoProvider(packageInfo) + + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + appInfoProvider.FooterAppVersion(false) + } + } + composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist() + } + private companion object { const val LABEL = "Label" const val VERSION_NAME = "VersionName" + const val PACKAGE_NAME = "package.name" val APP = object : ApplicationInfo() { override fun loadLabel(pm: PackageManager) = LABEL } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt index abdcd3bc5527..2fd1b10dade8 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -43,7 +43,6 @@ class AppListSwitchItemTest { fun appLabel_displayed() { composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(null), changeable = stateOf(false), onCheckedChange = {}, @@ -57,7 +56,6 @@ class AppListSwitchItemTest { fun summary_displayed() { composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(null), changeable = stateOf(false), onCheckedChange = {}, @@ -68,27 +66,9 @@ class AppListSwitchItemTest { } @Test - fun title_onClick() { - var titleClicked = false - composeTestRule.setContent { - ITEM_MODEL.AppListSwitchItem( - onClick = { titleClicked = true }, - checked = stateOf(false), - changeable = stateOf(false), - onCheckedChange = {}, - ) - } - - composeTestRule.onNodeWithText(LABEL).performClick() - - assertThat(titleClicked).isTrue() - } - - @Test fun switch_checkIsNull() { composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(null), changeable = stateOf(false), onCheckedChange = {}, @@ -102,7 +82,6 @@ class AppListSwitchItemTest { fun switch_checked() { composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(true), changeable = stateOf(false), onCheckedChange = {}, @@ -116,7 +95,6 @@ class AppListSwitchItemTest { fun switch_notChecked() { composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(false), changeable = stateOf(false), onCheckedChange = {}, @@ -130,7 +108,6 @@ class AppListSwitchItemTest { fun switch_changeable() { composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(false), changeable = stateOf(true), onCheckedChange = {}, @@ -144,7 +121,6 @@ class AppListSwitchItemTest { fun switch_notChangeable() { composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(false), changeable = stateOf(false), onCheckedChange = {}, @@ -159,7 +135,6 @@ class AppListSwitchItemTest { var switchClicked = false composeTestRule.setContent { ITEM_MODEL.AppListSwitchItem( - onClick = {}, checked = stateOf(false), changeable = stateOf(true), onCheckedChange = { switchClicked = true }, diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt new file mode 100644 index 000000000000..6e7fc8e4416c --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt @@ -0,0 +1,189 @@ +/* + * 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.settingslib.spaprivileged.template.app + +import android.content.pm.ApplicationInfo +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.assertIsOff +import androidx.compose.ui.test.assertIsOn +import androidx.compose.ui.test.isToggleable +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AppListTwoTargetSwitchItemTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun appLabel_displayed() { + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(null), + changeable = stateOf(false), + onCheckedChange = {}, + ) + } + + composeTestRule.onNodeWithText(LABEL).assertIsDisplayed() + } + + @Test + fun summary_displayed() { + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(null), + changeable = stateOf(false), + onCheckedChange = {}, + ) + } + + composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed() + } + + @Test + fun title_onClick() { + var titleClicked = false + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = { titleClicked = true }, + checked = stateOf(false), + changeable = stateOf(false), + onCheckedChange = {}, + ) + } + + composeTestRule.onNodeWithText(LABEL).performClick() + + assertThat(titleClicked).isTrue() + } + + @Test + fun switch_checkIsNull() { + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(null), + changeable = stateOf(false), + onCheckedChange = {}, + ) + } + + composeTestRule.onNode(isToggleable()).assertDoesNotExist() + } + + @Test + fun switch_checked() { + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(true), + changeable = stateOf(false), + onCheckedChange = {}, + ) + } + + composeTestRule.onNode(isToggleable()).assertIsOn() + } + + @Test + fun switch_notChecked() { + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(false), + changeable = stateOf(false), + onCheckedChange = {}, + ) + } + + composeTestRule.onNode(isToggleable()).assertIsOff() + } + + @Test + fun switch_changeable() { + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(false), + changeable = stateOf(true), + onCheckedChange = {}, + ) + } + + composeTestRule.onNode(isToggleable()).assertIsEnabled() + } + + @Test + fun switch_notChangeable() { + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(false), + changeable = stateOf(false), + onCheckedChange = {}, + ) + } + + composeTestRule.onNode(isToggleable()).assertIsNotEnabled() + } + + @Test + fun switch_onClick() { + var switchClicked = false + composeTestRule.setContent { + ITEM_MODEL.AppListTwoTargetSwitchItem( + onClick = {}, + checked = stateOf(false), + changeable = stateOf(true), + onCheckedChange = { switchClicked = true }, + ) + } + + composeTestRule.onNode(isToggleable()).performClick() + + assertThat(switchClicked).isTrue() + } + + private companion object { + const val PACKAGE_NAME = "package.name" + const val LABEL = "Label" + const val SUMMARY = "Summary" + val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + } + val ITEM_MODEL = AppListItemModel( + record = object : AppRecord { + override val app = APP + }, + label = LABEL, + summary = stateOf(SUMMARY), + ) + } +} diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index 612a9282fece..1a938d6ec37e 100644 --- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -179,7 +179,8 @@ public abstract class Tile implements Parcelable { * Check whether tile has order. */ public boolean hasOrder() { - return mMetaData.containsKey(META_DATA_KEY_ORDER) + return mMetaData != null + && mMetaData.containsKey(META_DATA_KEY_ORDER) && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer; } @@ -204,7 +205,7 @@ public abstract class Tile implements Parcelable { CharSequence title = null; ensureMetadataNotStale(context); final PackageManager packageManager = context.getPackageManager(); - if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) { + if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) { if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) { // If has as uri to provide dynamic title, skip loading here. UI will later load // at tile binding time. 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/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 7a48838ced91..bff51e32c1f9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -496,16 +496,6 @@ public abstract class InfoMediaManager extends MediaManager { return info.getName(); } - boolean shouldDisableMediaOutput(String packageName) { - if (TextUtils.isEmpty(packageName)) { - Log.w(TAG, "shouldDisableMediaOutput() package name is null or empty!"); - return true; - } - - // Disable when there is no transferable route - return getTransferableRoutes(packageName).isEmpty(); - } - @TargetApi(Build.VERSION_CODES.R) boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { return sessionInfo.isSystemSession() // System sessions are not remote diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 8479df1c7666..fd5ca847877f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -453,13 +453,6 @@ public class LocalMediaManager implements BluetoothCallback { } /** - * Returns {@code true} if needed to disable media output, otherwise returns {@code false}. - */ - public boolean shouldDisableMediaOutput(String packageName) { - return mInfoMediaManager.shouldDisableMediaOutput(packageName); - } - - /** * Returns {@code true} if needed to enable volume seekbar, otherwise returns {@code false}. */ public boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { 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/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 45b5de4cf367..866ef9d6076e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -1083,51 +1083,4 @@ public class InfoMediaManagerTest { assertThat(device.getState()).isEqualTo(STATE_SELECTED); assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device); } - - @Test - public void shouldDisableMediaOutput_infosIsEmpty_returnsTrue() { - mShadowRouter2Manager.setTransferableRoutes(new ArrayList<>()); - - assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isTrue(); - } - - @Test - public void shouldDisableMediaOutput_infosSizeEqual1_returnsFalse() { - final MediaRoute2Info info = mock(MediaRoute2Info.class); - final List<MediaRoute2Info> infos = new ArrayList<>(); - infos.add(info); - mShadowRouter2Manager.setTransferableRoutes(infos); - - when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER); - - assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse(); - } - - @Test - public void shouldDisableMediaOutput_infosSizeEqual1AndNotCastDevice_returnsFalse() { - final MediaRoute2Info info = mock(MediaRoute2Info.class); - final List<MediaRoute2Info> infos = new ArrayList<>(); - infos.add(info); - mShadowRouter2Manager.setTransferableRoutes(infos); - - when(info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); - - assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse(); - } - - - @Test - public void shouldDisableMediaOutput_infosSizeOverThan1_returnsFalse() { - final MediaRoute2Info info = mock(MediaRoute2Info.class); - final MediaRoute2Info info2 = mock(MediaRoute2Info.class); - final List<MediaRoute2Info> infos = new ArrayList<>(); - infos.add(info); - infos.add(info2); - mShadowRouter2Manager.setTransferableRoutes(infos); - - when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER); - when(info2.getType()).thenReturn(TYPE_REMOTE_SPEAKER); - - assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse(); - } } 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/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 18753fd9c0c7..006fc09fb400 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -422,9 +422,6 @@ -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt @@ -696,7 +693,6 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml index 57b3acd6557a..66c54f2a668e 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml @@ -21,6 +21,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/keyguard_lock_padding" + android:importantForAccessibility="no" android:ellipsize="marquee" android:focusable="true" android:gravity="center" 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/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 2377057f1fc5..d9b7bde66c67 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -69,7 +69,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED), 0.6f /* translationScale */, 0.45f /* delayScale */, AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.fast_out_linear_in)); + mContext, android.R.interpolator.fast_out_linear_in)); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry); @@ -82,8 +82,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { } void onDevicePostureChanged(@DevicePostureInt int posture) { - mLastDevicePosture = posture; - updateMargins(); + if (mLastDevicePosture != posture) { + mLastDevicePosture = posture; + updateMargins(); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 38c07dc98471..2bdf46e1309d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -104,8 +104,10 @@ public class KeyguardPatternView extends KeyguardInputView } void onDevicePostureChanged(@DevicePostureInt int posture) { - mLastDevicePosture = posture; - updateMargins(); + if (mLastDevicePosture != posture) { + mLastDevicePosture = posture; + updateMargins(); + } } private void updateMargins() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 5e84a57b488f..35198deab55f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -164,13 +164,13 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent; -import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.AuthenticationStatus; -import com.android.systemui.keyguard.shared.model.DetectionStatus; -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus; +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus; import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; @@ -1471,27 +1471,31 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private FaceAuthenticationListener mFaceAuthenticationListener = new FaceAuthenticationListener() { @Override - public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) { - if (status instanceof AcquiredAuthenticationStatus) { + public void onAuthenticationStatusChanged( + @NonNull FaceAuthenticationStatus status + ) { + if (status instanceof AcquiredFaceAuthenticationStatus) { handleFaceAcquired( - ((AcquiredAuthenticationStatus) status).getAcquiredInfo()); - } else if (status instanceof ErrorAuthenticationStatus) { - ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status; + ((AcquiredFaceAuthenticationStatus) status).getAcquiredInfo()); + } else if (status instanceof ErrorFaceAuthenticationStatus) { + ErrorFaceAuthenticationStatus error = + (ErrorFaceAuthenticationStatus) status; handleFaceError(error.getMsgId(), error.getMsg()); - } else if (status instanceof FailedAuthenticationStatus) { + } else if (status instanceof FailedFaceAuthenticationStatus) { handleFaceAuthFailed(); - } else if (status instanceof HelpAuthenticationStatus) { - HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status; + } else if (status instanceof HelpFaceAuthenticationStatus) { + HelpFaceAuthenticationStatus helpMsg = + (HelpFaceAuthenticationStatus) status; handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg()); - } else if (status instanceof SuccessAuthenticationStatus) { + } else if (status instanceof SuccessFaceAuthenticationStatus) { FaceManager.AuthenticationResult result = - ((SuccessAuthenticationStatus) status).getSuccessResult(); + ((SuccessFaceAuthenticationStatus) status).getSuccessResult(); handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric()); } } @Override - public void onDetectionStatusChanged(@NonNull DetectionStatus status) { + public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) { handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric()); } }; diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt index 2abdb849cd9a..e3e9b3a3754a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt @@ -16,10 +16,10 @@ package com.android.systemui.bouncer.domain.interactor +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.time.SystemClock @@ -35,8 +35,8 @@ constructor( private val keyguardStateController: KeyguardStateController, private val bouncerRepository: KeyguardBouncerRepository, private val biometricSettingsRepository: BiometricSettingsRepository, - private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val systemClock: SystemClock, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) { var receivedDownTouch = false val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible @@ -78,7 +78,7 @@ constructor( biometricSettingsRepository.isFingerprintEnrolled.value && biometricSettingsRepository.isStrongBiometricAllowed.value && biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value && - !deviceEntryFingerprintAuthRepository.isLockedOut.value && + !keyguardUpdateMonitor.isFingerprintLockedOut && !keyguardStateController.isUnlocked && !statusBarStateController.isDozing } 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 9a32e94180c7..e0834bb894b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -163,9 +163,11 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.SystemClock; +import com.android.systemui.wallpapers.data.repository.WallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; import dagger.Lazy; @@ -290,6 +292,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last"; public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"; private final DreamOverlayStateController mDreamOverlayStateController; + private final JavaAdapter mJavaAdapter; + private final WallpaperRepository mWallpaperRepository; /** The stream type that the lock sounds are tied to. */ private int mUiSoundsStreamType; @@ -1323,6 +1327,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, KeyguardTransitions keyguardTransitions, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + JavaAdapter javaAdapter, + WallpaperRepository wallpaperRepository, Lazy<ShadeController> shadeControllerLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -1384,6 +1390,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mScreenOffAnimationController = screenOffAnimationController; mInteractionJankMonitor = interactionJankMonitor; mDreamOverlayStateController = dreamOverlayStateController; + mJavaAdapter = javaAdapter; + mWallpaperRepository = wallpaperRepository; mActivityLaunchAnimator = activityLaunchAnimator; mScrimControllerLazy = scrimControllerLazy; @@ -1487,6 +1495,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, com.android.internal.R.anim.lock_screen_behind_enter); mWorkLockController = new WorkLockActivityController(mContext, mUserTracker); + + mJavaAdapter.alwaysCollectFlow( + mWallpaperRepository.getWallpaperSupportsAmbientMode(), + this::setWallpaperSupportsAmbientMode); } // TODO(b/273443374) remove, temporary util to get a feature flag @@ -2970,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)) { @@ -3191,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)); } @@ -3425,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); } /** @@ -3464,7 +3479,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it * with the light reveal scrim. */ - public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { + private void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { mWallpaperSupportsAmbientMode = supportsAmbientMode; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 1487408f1a0c..a5ac7c7500e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -67,9 +67,11 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.SystemClock; +import com.android.systemui.wallpapers.data.repository.WallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; import dagger.Lazy; @@ -131,6 +133,8 @@ public class KeyguardModule { KeyguardTransitions keyguardTransitions, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + JavaAdapter javaAdapter, + WallpaperRepository wallpaperRepository, Lazy<ShadeController> shadeController, Lazy<NotificationShadeWindowController> notificationShadeWindowController, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -172,6 +176,8 @@ public class KeyguardModule { keyguardTransitions, interactionJankMonitor, dreamOverlayStateController, + javaAdapter, + wallpaperRepository, shadeController, notificationShadeWindowController, activityLaunchAnimator, 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/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index a3d1abedcc2c..6edf40f47521 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -38,13 +38,13 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus -import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus -import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus -import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus +import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.log.SessionTracker @@ -88,10 +88,10 @@ interface DeviceEntryFaceAuthRepository { val canRunFaceAuth: StateFlow<Boolean> /** Provide the current status of face authentication. */ - val authenticationStatus: Flow<AuthenticationStatus> + val authenticationStatus: Flow<FaceAuthenticationStatus> /** Provide the current status of face detection. */ - val detectionStatus: Flow<DetectionStatus> + val detectionStatus: Flow<FaceDetectionStatus> /** Current state of whether face authentication is locked out or not. */ val isLockedOut: StateFlow<Boolean> @@ -151,13 +151,13 @@ constructor( private var cancelNotReceivedHandlerJob: Job? = null private var halErrorRetryJob: Job? = null - private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> = + private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> = MutableStateFlow(null) - override val authenticationStatus: Flow<AuthenticationStatus> + override val authenticationStatus: Flow<FaceAuthenticationStatus> get() = _authenticationStatus.filterNotNull() - private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null) - override val detectionStatus: Flow<DetectionStatus> + private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null) + override val detectionStatus: Flow<FaceDetectionStatus> get() = _detectionStatus.filterNotNull() private val _isLockedOut = MutableStateFlow(false) @@ -396,18 +396,18 @@ constructor( private val faceAuthCallback = object : FaceManager.AuthenticationCallback() { override fun onAuthenticationFailed() { - _authenticationStatus.value = FailedAuthenticationStatus + _authenticationStatus.value = FailedFaceAuthenticationStatus _isAuthenticated.value = false faceAuthLogger.authenticationFailed() onFaceAuthRequestCompleted() } override fun onAuthenticationAcquired(acquireInfo: Int) { - _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo) + _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo) } override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { - val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString()) + val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString()) if (errorStatus.isLockoutError()) { _isLockedOut.value = true } @@ -433,11 +433,11 @@ constructor( if (faceAcquiredInfoIgnoreList.contains(code)) { return } - _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString()) + _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString()) } override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) { - _authenticationStatus.value = SuccessAuthenticationStatus(result) + _authenticationStatus.value = SuccessFaceAuthenticationStatus(result) _isAuthenticated.value = true faceAuthLogger.faceAuthSuccess(result) onFaceAuthRequestCompleted() @@ -482,7 +482,7 @@ constructor( private val detectionCallback = FaceManager.FaceDetectionCallback { sensorId, userId, isStrong -> faceAuthLogger.faceDetected() - _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong) + _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong) } private var cancellationInProgress = false 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 52234b32b83a..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 @@ -21,27 +21,29 @@ import android.hardware.biometrics.BiometricAuthenticator.Modality import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.Dumpable import com.android.systemui.biometrics.AuthController import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dump.DumpManager -import java.io.PrintWriter +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +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 import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** Encapsulates state about device entry fingerprint auth mechanism. */ interface DeviceEntryFingerprintAuthRepository { /** Whether the device entry fingerprint auth is locked out. */ - val isLockedOut: StateFlow<Boolean> + val isLockedOut: Flow<Boolean> /** * Whether the fingerprint sensor is currently listening, this doesn't mean that the user is @@ -53,6 +55,9 @@ interface DeviceEntryFingerprintAuthRepository { * Fingerprint sensor type present on the device, null if fingerprint sensor is not available. */ val availableFpSensorType: Flow<BiometricType?> + + /** Provide the current status of fingerprint authentication. */ + val authenticationStatus: Flow<FingerprintAuthenticationStatus> } /** @@ -69,16 +74,7 @@ constructor( val authController: AuthController, val keyguardUpdateMonitor: KeyguardUpdateMonitor, @Application scope: CoroutineScope, - dumpManager: DumpManager, -) : DeviceEntryFingerprintAuthRepository, Dumpable { - - init { - dumpManager.registerDumpable(this) - } - - override fun dump(pw: PrintWriter, args: Array<String?>) { - pw.println("isLockedOut=${isLockedOut.value}") - } +) : DeviceEntryFingerprintAuthRepository { override val availableFpSensorType: Flow<BiometricType?> get() { @@ -114,7 +110,7 @@ constructor( else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null } - override val isLockedOut: StateFlow<Boolean> = + override val isLockedOut: Flow<Boolean> = conflatedCallbackFlow { val sendLockoutUpdate = fun() { @@ -138,7 +134,7 @@ constructor( sendLockoutUpdate() awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } - .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false) + .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) override val isRunning: Flow<Boolean> get() = conflatedCallbackFlow { @@ -166,6 +162,93 @@ constructor( awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } + override val authenticationStatus: Flow<FingerprintAuthenticationStatus> + get() = conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricAuthenticated( + userId: Int, + biometricSourceType: BiometricSourceType, + isStrongBiometric: Boolean, + ) { + + sendUpdateIfFingerprint( + biometricSourceType, + SuccessFingerprintAuthenticationStatus( + userId, + isStrongBiometric, + ), + ) + } + + override fun onBiometricError( + msgId: Int, + errString: String?, + biometricSourceType: BiometricSourceType, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + ErrorFingerprintAuthenticationStatus( + msgId, + errString, + ), + ) + } + + override fun onBiometricHelp( + msgId: Int, + helpString: String?, + biometricSourceType: BiometricSourceType, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + HelpFingerprintAuthenticationStatus( + msgId, + helpString, + ), + ) + } + + override fun onBiometricAuthFailed( + biometricSourceType: BiometricSourceType, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + FailFingerprintAuthenticationStatus, + ) + } + + override fun onBiometricAcquired( + biometricSourceType: BiometricSourceType, + acquireInfo: Int, + ) { + sendUpdateIfFingerprint( + biometricSourceType, + AcquiredFingerprintAuthenticationStatus( + acquireInfo, + ), + ) + } + + private fun sendUpdateIfFingerprint( + biometricSourceType: BiometricSourceType, + authenticationStatus: FingerprintAuthenticationStatus + ) { + if (biometricSourceType != BiometricSourceType.FINGERPRINT) { + return + } + + trySendWithFailureLogging( + authenticationStatus, + TAG, + "new fingerprint authentication status" + ) + } + } + keyguardUpdateMonitor.registerCallback(callback) + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 42bee4a3bdcd..7475c4251211 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -31,11 +31,13 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController +import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel +import com.android.systemui.keyguard.shared.model.ScreenModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -49,9 +51,11 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn @@ -148,6 +152,9 @@ interface KeyguardRepository { /** Observable for device wake/sleep state */ val wakefulness: StateFlow<WakefulnessModel> + /** Observable for device screen state */ + val screenModel: StateFlow<ScreenModel> + /** Observable for biometric unlock modes */ val biometricUnlockState: Flow<BiometricUnlockModel> @@ -163,6 +170,9 @@ interface KeyguardRepository { /** Whether quick settings or quick-quick settings is visible. */ val isQuickSettingsVisible: Flow<Boolean> + /** Receive an event for doze time tick */ + val dozeTimeTick: Flow<Unit> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -204,6 +214,8 @@ interface KeyguardRepository { fun setIsDozing(isDozing: Boolean) fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) + + fun dozeTimeTick() } /** Encapsulates application state for the keyguard. */ @@ -213,6 +225,7 @@ class KeyguardRepositoryImpl constructor( statusBarStateController: StatusBarStateController, wakefulnessLifecycle: WakefulnessLifecycle, + screenLifecycle: ScreenLifecycle, biometricUnlockController: BiometricUnlockController, private val keyguardStateController: KeyguardStateController, private val keyguardBypassController: KeyguardBypassController, @@ -370,6 +383,13 @@ constructor( _isDozing.value = isDozing } + private val _dozeTimeTick = MutableSharedFlow<Unit>() + override val dozeTimeTick = _dozeTimeTick.asSharedFlow() + + override fun dozeTimeTick() { + _dozeTimeTick.tryEmit(Unit) + } + private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow() @@ -559,6 +579,42 @@ constructor( initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle), ) + override val screenModel: StateFlow<ScreenModel> = + conflatedCallbackFlow { + val observer = + object : ScreenLifecycle.Observer { + override fun onScreenTurningOn() { + dispatchNewState() + } + override fun onScreenTurnedOn() { + dispatchNewState() + } + override fun onScreenTurningOff() { + dispatchNewState() + } + override fun onScreenTurnedOff() { + dispatchNewState() + } + + private fun dispatchNewState() { + trySendWithFailureLogging( + ScreenModel.fromScreenLifecycle(screenLifecycle), + TAG, + "updated screen state", + ) + } + } + + screenLifecycle.addObserver(observer) + awaitClose { screenLifecycle.removeObserver(observer) } + } + .stateIn( + scope, + // Use Eagerly so that we're always listening and never miss an event. + SharingStarted.Eagerly, + initialValue = ScreenModel.fromScreenLifecycle(screenLifecycle), + ) + override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow { fun sendFpLocation() { trySendWithFailureLogging( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt index abe59b76816f..27e3a749a6c0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt @@ -18,8 +18,8 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -40,10 +40,10 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA private val _canRunFaceAuth = MutableStateFlow(false) override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth - override val authenticationStatus: Flow<AuthenticationStatus> + override val authenticationStatus: Flow<FaceAuthenticationStatus> get() = emptyFlow() - override val detectionStatus: Flow<DetectionStatus> + override val detectionStatus: Flow<FaceDetectionStatus> get() = emptyFlow() private val _isLockedOut = MutableStateFlow(false) 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/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt index 2efcd0c1ffe7..0c898befe6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt @@ -35,4 +35,8 @@ constructor( fun setLastTapToWakePosition(position: Point) { keyguardRepository.setLastDozeTapToWakePosition(position) } + + fun dozeTimeTick() { + keyguardRepository.dozeTimeTick() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 74ef7a50fd44..141b13055889 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -16,8 +16,8 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import kotlinx.coroutines.flow.Flow /** @@ -27,10 +27,10 @@ import kotlinx.coroutines.flow.Flow interface KeyguardFaceAuthInteractor { /** Current authentication status */ - val authenticationStatus: Flow<AuthenticationStatus> + val authenticationStatus: Flow<FaceAuthenticationStatus> /** Current detection status */ - val detectionStatus: Flow<DetectionStatus> + val detectionStatus: Flow<FaceDetectionStatus> /** Can face auth be run right now */ fun canFaceAuthRun(): Boolean @@ -72,8 +72,8 @@ interface KeyguardFaceAuthInteractor { */ interface FaceAuthenticationListener { /** Receive face authentication status updates */ - fun onAuthenticationStatusChanged(status: AuthenticationStatus) + fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) /** Receive status updates whenever face detection runs */ - fun onDetectionStatusChanged(status: DetectionStatus) + fun onDetectionStatusChanged(status: FaceDetectionStatus) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 7fae7522d981..1553525915d5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -70,6 +70,8 @@ constructor( val dozeAmount: Flow<Float> = repository.linearDozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing + /** Receive an event for doze time tick */ + val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick /** Whether Always-on Display mode is available. */ val isAodAvailable: Flow<Boolean> = repository.isAodAvailable /** Doze transition information. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt index 5005b6c7f0df..10dd900e437e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -31,9 +31,9 @@ import kotlinx.coroutines.flow.emptyFlow */ @SysUISingleton class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor { - override val authenticationStatus: Flow<AuthenticationStatus> + override val authenticationStatus: Flow<FaceAuthenticationStatus> get() = emptyFlow() - override val detectionStatus: Flow<DetectionStatus> + override val detectionStatus: Flow<FaceDetectionStatus> get() = emptyFlow() override fun canFaceAuthRun(): Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index d467225a9d63..6e7a20b092f4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -30,8 +30,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.util.kotlin.pairwise @@ -165,10 +165,10 @@ constructor( repository.cancel() } - private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null) + private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null) /** Provide the status of face authentication */ override val authenticationStatus = - merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus) + merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus) /** Provide the status of face detection */ override val detectionStatus = repository.detectionStatus @@ -176,13 +176,13 @@ constructor( private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { if (repository.isLockedOut.value) { - _authenticationStatusOverride.value = - ErrorAuthenticationStatus( + faceAuthenticationStatusOverride.value = + ErrorFaceAuthenticationStatus( BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT, context.resources.getString(R.string.keyguard_face_unlock_unavailable) ) } else { - _authenticationStatusOverride.value = null + faceAuthenticationStatusOverride.value = null applicationScope.launch { faceAuthenticationLogger.authRequested(uiEvent) repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt index b354cfd27687..0f6d82ed4b5c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt @@ -23,33 +23,35 @@ import android.os.SystemClock.elapsedRealtime * Authentication status provided by * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository] */ -sealed class AuthenticationStatus +sealed class FaceAuthenticationStatus /** Success authentication status. */ -data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) : - AuthenticationStatus() +data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) : + FaceAuthenticationStatus() /** Face authentication help message. */ -data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus() +data class HelpFaceAuthenticationStatus(val msgId: Int, val msg: String?) : + FaceAuthenticationStatus() /** Face acquired message. */ -data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus() +data class AcquiredFaceAuthenticationStatus(val acquiredInfo: Int) : FaceAuthenticationStatus() /** Face authentication failed message. */ -object FailedAuthenticationStatus : AuthenticationStatus() +object FailedFaceAuthenticationStatus : FaceAuthenticationStatus() /** Face authentication error message */ -data class ErrorAuthenticationStatus( +data class ErrorFaceAuthenticationStatus( val msgId: Int, val msg: String? = null, // present to break equality check if the same error occurs repeatedly. val createdAt: Long = elapsedRealtime() -) : AuthenticationStatus() { +) : FaceAuthenticationStatus() { /** * Method that checks if [msgId] is a lockout error. A lockout error means that face * authentication is locked out. */ - fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT + fun isLockoutError() = + msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT || msgId == FaceManager.FACE_ERROR_LOCKOUT /** * Method that checks if [msgId] is a cancellation error. This means that face authentication @@ -64,4 +66,4 @@ data class ErrorAuthenticationStatus( } /** Face detection success message. */ -data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean) +data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean) 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 new file mode 100644 index 000000000000..7fc6016bf087 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt @@ -0,0 +1,52 @@ +/* + * 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.shared.model + +import android.os.SystemClock.elapsedRealtime + +/** + * Fingerprint authentication status provided by + * [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository] + */ +sealed class FingerprintAuthenticationStatus + +/** Fingerprint authentication success status. */ +data class SuccessFingerprintAuthenticationStatus( + val userId: Int, + val isStrongBiometric: Boolean, +) : FingerprintAuthenticationStatus() + +/** Fingerprint authentication help message. */ +data class HelpFingerprintAuthenticationStatus( + val msgId: Int, + val msg: String?, +) : FingerprintAuthenticationStatus() + +/** Fingerprint acquired message. */ +data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) : + FingerprintAuthenticationStatus() + +/** Fingerprint authentication failed message. */ +object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() + +/** Fingerprint authentication error message */ +data class ErrorFingerprintAuthenticationStatus( + val msgId: Int, + val msg: String? = null, + // present to break equality check if the same error occurs repeatedly. + val createdAt: Long = elapsedRealtime(), +) : FingerprintAuthenticationStatus() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt new file mode 100644 index 000000000000..80a1b75c4350 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt @@ -0,0 +1,29 @@ +/* + * 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.shared.model + +import com.android.systemui.keyguard.ScreenLifecycle + +/** Model device screen lifecycle states. */ +data class ScreenModel( + val state: ScreenState, +) { + companion object { + fun fromScreenLifecycle(screenLifecycle: ScreenLifecycle): ScreenModel { + return ScreenModel(ScreenState.fromScreenLifecycleInt(screenLifecycle.getScreenState())) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt new file mode 100644 index 000000000000..fe5d9355a6fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import com.android.systemui.keyguard.ScreenLifecycle + +enum class ScreenState { + /** Screen is fully off. */ + SCREEN_OFF, + /** Signal that the screen is turning on. */ + SCREEN_TURNING_ON, + /** Screen is fully on. */ + SCREEN_ON, + /** Signal that the screen is turning off. */ + SCREEN_TURNING_OFF; + + companion object { + fun fromScreenLifecycleInt(value: Int): ScreenState { + return when (value) { + ScreenLifecycle.SCREEN_OFF -> SCREEN_OFF + ScreenLifecycle.SCREEN_TURNING_ON -> SCREEN_TURNING_ON + ScreenLifecycle.SCREEN_ON -> SCREEN_ON + ScreenLifecycle.SCREEN_TURNING_OFF -> SCREEN_TURNING_OFF + else -> throw IllegalArgumentException("Invalid screen value: $value") + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt index 728dd3911663..9872d97021fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -37,6 +37,7 @@ object UdfpsAodFingerprintViewBinder { view: LottieAnimationView, viewModel: UdfpsAodViewModel, ) { + view.alpha = 0f view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt index 26ef4685d286..0113628c30ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt @@ -38,6 +38,7 @@ object UdfpsBackgroundViewBinder { view: ImageView, viewModel: BackgroundViewModel, ) { + view.alpha = 0f view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt index 0ab8e52fb6c7..bab04f234b3f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -42,6 +42,7 @@ object UdfpsFingerprintViewBinder { view: LottieAnimationView, viewModel: FingerprintViewModel, ) { + view.alpha = 0f view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 68cdfb6d5865..373f70582612 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -4,7 +4,7 @@ import android.hardware.face.FaceManager import android.hardware.face.FaceSensorPropertiesInternal import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.FaceAuthLog @@ -240,7 +240,7 @@ constructor( ) } - fun hardwareError(errorStatus: ErrorAuthenticationStatus) { + fun hardwareError(errorStatus: ErrorFaceAuthenticationStatus) { logBuffer.log( TAG, DEBUG, diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 5aa5feeedcf1..f9324a95c1e5 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -16,19 +16,25 @@ package com.android.systemui.scene.ui.view +import android.view.Gravity +import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R import com.android.systemui.compose.ComposeFacade import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import java.time.Instant import kotlinx.coroutines.launch object SceneWindowRootViewBinder { @@ -77,6 +83,9 @@ object SceneWindowRootViewBinder { ) ) + val legacyView = view.requireViewById<View>(R.id.legacy_window_root) + view.addView(createVisibilityToggleView(legacyView)) + launch { viewModel.isVisible.collect { isVisible -> onVisibilityChangedInternal(isVisible) @@ -89,4 +98,28 @@ object SceneWindowRootViewBinder { } } } + + private var clickCount = 0 + private var lastClick = Instant.now() + + /** + * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by + * tapping 5 times in quick succession on the device camera (top center). + */ + // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy + // SysUI altogether. + private fun createVisibilityToggleView(otherView: View): View { + val toggleView = View(otherView.context) + toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL) + toggleView.setOnClickListener { + val now = Instant.now() + clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1 + if (clickCount == 5) { + otherView.isVisible = !otherView.isVisible + clickCount = 0 + } + lastClick = now + } + return toggleView + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index bb2be66cffda..b328c5564590 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -543,7 +543,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.forceUserActivity, state.launchingActivityFromNotification, state.mediaBackdropShowing, - state.wallpaperSupportsAmbientMode, state.windowNotTouchable, state.componentsForcingTopUi, state.forceOpenTokens, @@ -734,12 +733,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW apply(mCurrentState); } - @Override - public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { - mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode; - apply(mCurrentState); - } - /** * @param state The {@link StatusBarStateController} of the status bar. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index 7812f07fc59c..d25294343d2f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -46,7 +46,6 @@ class NotificationShadeWindowState( @JvmField var forceUserActivity: Boolean = false, @JvmField var launchingActivityFromNotification: Boolean = false, @JvmField var mediaBackdropShowing: Boolean = false, - @JvmField var wallpaperSupportsAmbientMode: Boolean = false, @JvmField var windowNotTouchable: Boolean = false, @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(), @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(), @@ -84,7 +83,6 @@ class NotificationShadeWindowState( forceUserActivity.toString(), launchingActivityFromNotification.toString(), mediaBackdropShowing.toString(), - wallpaperSupportsAmbientMode.toString(), windowNotTouchable.toString(), componentsForcingTopUi.toString(), forceOpenTokens.toString(), @@ -124,7 +122,6 @@ class NotificationShadeWindowState( forceUserActivity: Boolean, launchingActivity: Boolean, backdropShowing: Boolean, - wallpaperSupportsAmbientMode: Boolean, notTouchable: Boolean, componentsForcingTopUi: MutableSet<String>, forceOpenTokens: MutableSet<Any>, @@ -153,7 +150,6 @@ class NotificationShadeWindowState( this.forceUserActivity = forceUserActivity this.launchingActivityFromNotification = launchingActivity this.mediaBackdropShowing = backdropShowing - this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode this.windowNotTouchable = notTouchable this.componentsForcingTopUi.clear() this.componentsForcingTopUi.addAll(componentsForcingTopUi) @@ -200,7 +196,6 @@ class NotificationShadeWindowState( "forceUserActivity", "launchingActivity", "backdropShowing", - "wallpaperSupportsAmbientMode", "notTouchable", "componentsForcingTopUi", "forceOpenTokens", 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/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 47a4641bcdd9..5ac542b3530f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -112,9 +112,6 @@ public interface NotificationShadeWindowController extends RemoteInputController /** Sets the state of whether heads up is showing or not. */ default void setHeadsUpShowing(boolean showing) {} - /** Sets whether the wallpaper supports ambient mode or not. */ - default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {} - /** Gets whether the wallpaper is showing or not. */ default boolean isShowingWallpaper() { return false; 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/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 2b9c3d33e9b8..acd6e49fe8c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -259,8 +259,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void readyForKeyguardDone(); - void setLockscreenUser(int newUserId); - void showKeyguard(); boolean hideKeyguard(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 5fb729cb26f1..8361d6ba1801 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -51,7 +51,6 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.TaskInfo; import android.app.UiModeManager; -import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -980,16 +979,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { createAndAddWindows(result); - if (mWallpaperSupported) { - // Make sure we always have the most current wallpaper info. - IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); - mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter, - null /* handler */, UserHandle.ALL); - mWallpaperChangedReceiver.onReceive(mContext, null); - } else if (DEBUG) { - Log.v(TAG, "start(): no wallpaper service "); - } - // Set up the initial notification state. This needs to happen before CommandQueue.disable() setUpPresenter(); @@ -2164,18 +2153,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; /** - * Notify the shade controller that the current user changed - * - * @param newUserId userId of the new user - */ - @Override - public void setLockscreenUser(int newUserId) { - if (mWallpaperSupported) { - mWallpaperChangedReceiver.onReceive(mContext, null); - } - } - - /** * Reload some of our resources when the configuration changes. * * We don't reload everything when the configuration changes -- we probably @@ -3549,33 +3526,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } }; - /** - * @deprecated See {@link com.android.systemui.wallpapers.data.repository.WallpaperRepository} - * instead. - */ - private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (!mWallpaperSupported) { - // Receiver should not have been registered at all... - Log.wtf(TAG, "WallpaperManager not supported"); - return; - } - WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser( - mUserTracker.getUserId()); - mWallpaperController.onWallpaperInfoUpdated(info); - - final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_dozeSupportsAodWallpaper); - // If WallpaperInfo is null, it must be ImageWallpaper. - final boolean supportsAmbientMode = deviceSupportsAodWallpaper - && (info != null && info.supportsAmbientMode()); - - mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); - mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode); - } - }; - private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override public void onConfigChanged(Configuration newConfig) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 7312db6595e5..ed9722e04da0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -311,6 +311,7 @@ public final class DozeServiceHost implements DozeHost { @Override public void dozeTimeTick() { + mDozeInteractor.dozeTimeTick(); mNotificationPanel.dozeTimeTick(); mAuthController.dozeTimeTick(); if (mAmbientIndicationContainer instanceof DozeReceiver) { 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 e63fecd33383..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; @@ -208,7 +204,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu // End old BaseStatusBar.userSwitched mCommandQueue.animateCollapsePanels(); mMediaManager.clearCurrentMediaNotification(); - mCentralSurfaces.setLockscreenUser(newUserId); updateMediaMetaData(true, false); } @@ -284,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/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 27cc64f9a8e8..0e99c67a0d80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -19,12 +19,10 @@ package com.android.systemui.statusbar.pipeline.dagger import android.net.wifi.WifiManager import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory -import com.android.systemui.log.LogBuffer -import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel -import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel @@ -46,6 +44,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher @@ -58,9 +58,9 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap -import kotlinx.coroutines.flow.Flow import java.util.function.Supplier import javax.inject.Named +import kotlinx.coroutines.flow.Flow @Module abstract class StatusBarPipelineModule { diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 5d09e064604a..a501e87902a8 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -20,6 +20,11 @@ import android.content.Intent; import com.android.systemui.Dependency; +/** + * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead + * or {@code SettingsObserver} to be able to specify the handler. + */ +@Deprecated public abstract class TunerService { public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER"; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 8cfe2eac3d33..ccc0a79d2cfe 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -56,7 +56,11 @@ import javax.inject.Inject; /** + * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead + * or {@code SettingsObserver} to be able to specify the handler. + * This class will interact with SecureSettings using the main looper. */ +@Deprecated @SysUISingleton public class TunerServiceImpl extends TunerService { diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt index db2aca873d0c..65a02184f96d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt @@ -16,32 +16,34 @@ package com.android.systemui.util -import android.app.WallpaperInfo import android.app.WallpaperManager import android.util.Log import android.view.View import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.wallpapers.data.repository.WallpaperRepository import javax.inject.Inject import kotlin.math.max private const val TAG = "WallpaperController" +/** + * Controller for wallpaper-related logic. + * + * Note: New logic should be added to [WallpaperRepository], not this class. + */ @SysUISingleton -class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) { +class WallpaperController @Inject constructor( + private val wallpaperManager: WallpaperManager, + private val wallpaperRepository: WallpaperRepository, +) { var rootView: View? = null private var notificationShadeZoomOut: Float = 0f private var unfoldTransitionZoomOut: Float = 0f - private var wallpaperInfo: WallpaperInfo? = null - - fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) { - this.wallpaperInfo = wallpaperInfo - } - private val shouldUseDefaultUnfoldTransition: Boolean - get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition() + get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition() ?: true fun setNotificationShadeZoom(zoomOut: Float) { diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt index a64058968581..b45b8cd15bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt @@ -16,9 +16,11 @@ package com.android.systemui.wallpapers.data.repository +import android.app.WallpaperInfo import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** @@ -29,5 +31,6 @@ import kotlinx.coroutines.flow.asStateFlow */ @SysUISingleton class NoopWallpaperRepository @Inject constructor() : WallpaperRepository { + override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow() override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt index 48895ffcacb9..b8f95832b852 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.wallpapers.data.repository +import android.app.WallpaperInfo import android.app.WallpaperManager import android.content.Context import android.content.Intent @@ -36,11 +37,15 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn /** A repository storing information about the current wallpaper. */ interface WallpaperRepository { + /** Emits the current user's current wallpaper. */ + val wallpaperInfo: StateFlow<WallpaperInfo?> + /** Emits true if the current user's current wallpaper supports ambient mode. */ val wallpaperSupportsAmbientMode: StateFlow<Boolean> } @@ -78,28 +83,35 @@ constructor( // Only update the wallpaper status once the user selection has finished. .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE } - override val wallpaperSupportsAmbientMode: StateFlow<Boolean> = + override val wallpaperInfo: StateFlow<WallpaperInfo?> = if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) { - MutableStateFlow(false).asStateFlow() + MutableStateFlow(null).asStateFlow() } else { combine(wallpaperChanged, selectedUser) { _, selectedUser -> - doesWallpaperSupportAmbientMode(selectedUser) + getWallpaper(selectedUser) } .stateIn( scope, // Always be listening for wallpaper changes. SharingStarted.Eagerly, - initialValue = - doesWallpaperSupportAmbientMode(userRepository.selectedUser.value), + initialValue = getWallpaper(userRepository.selectedUser.value), ) } - private fun doesWallpaperSupportAmbientMode(selectedUser: SelectedUserModel): Boolean { - return wallpaperManager - .getWallpaperInfoForUser( - selectedUser.userInfo.id, + override val wallpaperSupportsAmbientMode: StateFlow<Boolean> = + wallpaperInfo + .map { + // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode. + it?.supportsAmbientMode() == true + } + .stateIn( + scope, + // Always be listening for wallpaper changes. + SharingStarted.Eagerly, + initialValue = wallpaperInfo.value?.supportsAmbientMode() == true, ) - // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode. - ?.supportsAmbientMode() == true + + private fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? { + return wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 5d75428b8fb4..cb182297eae1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -76,7 +76,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> - @Mock private lateinit var mPostureController: DevicePostureController + @Mock private lateinit var mPostureController: DevicePostureController private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController private lateinit var fakeFeatureFlags: FakeFeatureFlags @@ -119,7 +119,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardPatternViewController.onViewAttached() - assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline()) + assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) } @Test @@ -131,15 +131,20 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardPatternViewController.onViewAttached() // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED - assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline()) + assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) // Simulate posture change to state DEVICE_POSTURE_OPENED with callback verify(mPostureController).addCallback(postureCallbackCaptor.capture()) val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) - // Verify view is now in posture state DEVICE_POSTURE_OPENED - assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline()) + // Simulate posture change to same state with callback + assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) + + postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Verify view is still in posture state DEVICE_POSTURE_OPENED + assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) } private fun getPatternTopGuideline(): Float { @@ -150,7 +155,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent } - private fun getExpectedTopGuideline(): Float { + private fun getHalfOpenedBouncerHeightRatio(): Float { return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index d256ee163877..4dc7652f83cf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -19,6 +19,8 @@ package com.android.keyguard import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils @@ -32,6 +34,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -51,7 +55,10 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class KeyguardPinViewControllerTest : SysuiTestCase() { - @Mock private lateinit var keyguardPinView: KeyguardPINView + + private lateinit var objectKeyguardPINView: KeyguardPINView + + @Mock private lateinit var mockKeyguardPinView: KeyguardPINView @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea @@ -83,64 +90,73 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Mock lateinit var deleteButton: NumPadButton @Mock lateinit var enterButton: View - private lateinit var pinViewController: KeyguardPinViewController - @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> @Before fun setup() { MockitoAnnotations.initMocks(this) - Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area)) + Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area)) .thenReturn(keyguardMessageArea) Mockito.`when`( keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java)) ) .thenReturn(keyguardMessageAreaController) - `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry) - `when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry)) + `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry) + `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry)) .thenReturn(passwordTextView) - `when`(keyguardPinView.resources).thenReturn(context.resources) - `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) + `when`(mockKeyguardPinView.resources).thenReturn(context.resources) + `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) .thenReturn(deleteButton) - `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) + `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) // For posture tests: - `when`(keyguardPinView.buttons).thenReturn(arrayOf()) + `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf()) `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) - pinViewController = - KeyguardPinViewController( - keyguardPinView, - keyguardUpdateMonitor, - securityMode, - lockPatternUtils, - mKeyguardSecurityCallback, - keyguardMessageAreaControllerFactory, - mLatencyTracker, - liftToActivateListener, - mEmergencyButtonController, - falsingCollector, - postureController, - featureFlags - ) + objectKeyguardPINView = + View.inflate(mContext, R.layout.keyguard_pin_view, null) + .findViewById(R.id.keyguard_pin_view) as KeyguardPINView + } + + private fun constructPinViewController( + mKeyguardPinView: KeyguardPINView + ): KeyguardPinViewController { + return KeyguardPinViewController( + mKeyguardPinView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + mKeyguardSecurityCallback, + keyguardMessageAreaControllerFactory, + mLatencyTracker, + liftToActivateListener, + mEmergencyButtonController, + falsingCollector, + postureController, + featureFlags + ) } @Test - fun onViewAttached_deviceHalfFolded_propagatedToPinView() { - `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) + fun onViewAttached_deviceHalfFolded_propagatedToPatternView() { + val pinViewController = constructPinViewController(objectKeyguardPINView) + overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) + whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) pinViewController.onViewAttached() - verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED) + assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) } @Test - fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() { - `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) + fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() { + val pinViewController = constructPinViewController(objectKeyguardPINView) + overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) - // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED + whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) pinViewController.onViewAttached() - verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED) + // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED + assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) // Simulate posture change to state DEVICE_POSTURE_OPENED with callback verify(postureController).addCallback(postureCallbackCaptor.capture()) @@ -148,31 +164,57 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) // Verify view is now in posture state DEVICE_POSTURE_OPENED - verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED) + assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) + + // Simulate posture change to same state with callback + postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Verify view is still in posture state DEVICE_POSTURE_OPENED + assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) + } + + private fun getPinTopGuideline(): Float { + val cs = ConstraintSet() + val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout + cs.clone(container) + return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent + } + + private fun getHalfOpenedBouncerHeightRatio(): Float { + return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio) } @Test fun startAppearAnimation() { + val pinViewController = constructPinViewController(mockKeyguardPinView) + pinViewController.startAppearAnimation() + verify(keyguardMessageAreaController) .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false) } @Test fun startAppearAnimation_withExistingMessage() { + val pinViewController = constructPinViewController(mockKeyguardPinView) Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.") + pinViewController.startAppearAnimation() + verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean()) } @Test fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() { + val pinViewController = constructPinViewController(mockKeyguardPinView) `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) + `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3) `when`(passwordTextView.text).thenReturn("") pinViewController.startAppearAnimation() + verify(deleteButton).visibility = View.INVISIBLE verify(enterButton).visibility = View.INVISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -181,12 +223,15 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() { + val pinViewController = constructPinViewController(mockKeyguardPinView) `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) + `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6) `when`(passwordTextView.text).thenReturn("") pinViewController.startAppearAnimation() + verify(deleteButton).visibility = View.VISIBLE verify(enterButton).visibility = View.VISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -195,7 +240,10 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun handleLockout_readsNumberOfErrorAttempts() { + val pinViewController = constructPinViewController(mockKeyguardPinView) + pinViewController.handleAttemptLockout(0) + verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt()) } } 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/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index eaa31ac1d157..ecc776b98c6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -48,6 +48,7 @@ import android.view.WindowMetrics import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.airbnb.lottie.LottieAnimationView +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase @@ -64,7 +65,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor @@ -153,8 +153,8 @@ class SideFpsControllerTest : SysuiTestCase() { mock(KeyguardStateController::class.java), keyguardBouncerRepository, FakeBiometricSettingsRepository(), - FakeDeviceEntryFingerprintAuthRepository(), FakeSystemClock(), + mock(KeyguardUpdateMonitor::class.java), ) displayStateInteractor = DisplayStateInteractorImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 9df06dc9e18f..8dfeb3bde0c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -34,7 +34,6 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -106,8 +105,8 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : mock(KeyguardStateController::class.java), keyguardBouncerRepository, mock(BiometricSettingsRepository::class.java), - mock(DeviceEntryFingerprintAuthRepository::class.java), mock(SystemClock::class.java), + mKeyguardUpdateMonitor, ) return createUdfpsKeyguardViewController( /* useModernBouncer */ true, /* useExpandedOverlay */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index 37b9ca49ef57..186df02536ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository @@ -54,6 +55,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var bouncerLogger: TableLogBuffer + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Before fun setup() { @@ -72,8 +74,8 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { keyguardStateController, bouncerRepository, biometricSettingsRepository, - deviceEntryFingerprintAuthRepository, systemClock, + keyguardUpdateMonitor, ) } @@ -118,7 +120,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Test fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() { givenCanShowAlternateBouncer() - deviceEntryFingerprintAuthRepository.setLockedOut(true) + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true) assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } @@ -168,7 +170,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { biometricSettingsRepository.setFingerprintEnrolled(true) biometricSettingsRepository.setStrongBiometricAllowed(true) biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true) - deviceEntryFingerprintAuthRepository.setLockedOut(false) + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) whenever(keyguardStateController.isUnlocked).thenReturn(false) } 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/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index ddcbe7993697..00f67a32ff8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -78,6 +78,7 @@ import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardSecurityView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.TestScopeProvider; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.DejankUtils; import com.android.systemui.SysuiTestCase; @@ -115,9 +116,11 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.FakeSystemClock; +import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; import org.junit.After; @@ -132,6 +135,7 @@ import org.mockito.MockitoAnnotations; import kotlinx.coroutines.CoroutineDispatcher; import kotlinx.coroutines.flow.Flow; +import kotlinx.coroutines.test.TestScope; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -139,6 +143,9 @@ import kotlinx.coroutines.flow.Flow; public class KeyguardViewMediatorTest extends SysuiTestCase { private KeyguardViewMediator mViewMediator; + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); + private @Mock UserTracker mUserTracker; private @Mock DevicePolicyManager mDevicePolicyManager; private @Mock LockPatternUtils mLockPatternUtils; @@ -191,6 +198,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock SecureSettings mSecureSettings; private @Mock AlarmManager mAlarmManager; private FakeSystemClock mSystemClock; + private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository(); /** Most recent value passed to {@link KeyguardStateController#notifyKeyguardGoingAway}. */ private boolean mKeyguardGoingAway = false; @@ -928,6 +936,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKeyguardTransitions, mInteractionJankMonitor, mDreamOverlayStateController, + mJavaAdapter, + mWallpaperRepository, () -> mShadeController, () -> mNotificationShadeWindowController, () -> mActivityLaunchAnimator, 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/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 020c0b22ba27..47c662c3b4b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -36,6 +36,7 @@ import com.android.internal.logging.UiEventLogger import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase @@ -50,12 +51,12 @@ import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus -import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus +import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -113,6 +114,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { @Mock private lateinit var sessionTracker: SessionTracker @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Captor private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback> @@ -131,8 +133,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var testScope: TestScope private lateinit var fakeUserRepository: FakeUserRepository - private lateinit var authStatus: FlowValue<AuthenticationStatus?> - private lateinit var detectStatus: FlowValue<DetectionStatus?> + private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?> + private lateinit var detectStatus: FlowValue<FaceDetectionStatus?> private lateinit var authRunning: FlowValue<Boolean?> private lateinit var bypassEnabled: FlowValue<Boolean?> private lateinit var lockedOut: FlowValue<Boolean?> @@ -175,10 +177,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { AlternateBouncerInteractor( bouncerRepository = bouncerRepository, biometricSettingsRepository = biometricSettingsRepository, - deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository, systemClock = mock(SystemClock::class.java), keyguardStateController = FakeKeyguardStateController(), statusBarStateController = mock(StatusBarStateController::class.java), + keyguardUpdateMonitor = keyguardUpdateMonitor, ) bypassStateChangedListener = @@ -262,7 +264,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { val successResult = successResult() authenticationCallback.value.onAuthenticationSucceeded(successResult) - assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult)) + assertThat(authStatus()).isEqualTo(SuccessFaceAuthenticationStatus(successResult)) assertThat(authenticated()).isTrue() assertThat(authRunning()).isFalse() assertThat(canFaceAuthRun()).isFalse() @@ -383,7 +385,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { detectionCallback.value.onFaceDetected(1, 1, true) - assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true)) + assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true)) } @Test @@ -423,7 +425,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { FACE_ERROR_CANCELED, "First auth attempt cancellation completed" ) - val value = authStatus() as ErrorAuthenticationStatus + val value = authStatus() as ErrorFaceAuthenticationStatus assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED) assertThat(value.msg).isEqualTo("First auth attempt cancellation completed") @@ -465,7 +467,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg") authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg") - assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg")) + assertThat(authStatus()).isEqualTo(HelpFaceAuthenticationStatus(9, "help msg")) } @Test 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 264328b04227..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 @@ -26,7 +26,11 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus +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.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -49,7 +53,6 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var authController: AuthController @Captor private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback> @@ -68,7 +71,6 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { authController, keyguardUpdateMonitor, testScope.backgroundScope, - dumpManager, ) } @@ -177,4 +179,129 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT) assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT) } + + @Test + fun onFingerprintSuccess_successAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAuthenticated( + 0, + BiometricSourceType.FINGERPRINT, + true, + ) + + val status = authenticationStatus as SuccessFingerprintAuthenticationStatus + assertThat(status.userId).isEqualTo(0) + assertThat(status.isStrongBiometric).isEqualTo(true) + } + + @Test + fun onFingerprintFailed_failedAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAuthFailed( + BiometricSourceType.FINGERPRINT, + ) + + assertThat(authenticationStatus) + .isInstanceOf(FailFingerprintAuthenticationStatus::class.java) + } + + @Test + fun onFingerprintError_errorAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricError( + 1, + "test_string", + BiometricSourceType.FINGERPRINT, + ) + + val status = authenticationStatus as ErrorFingerprintAuthenticationStatus + assertThat(status.msgId).isEqualTo(1) + assertThat(status.msg).isEqualTo("test_string") + } + + @Test + fun onFingerprintHelp_helpAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricHelp( + 1, + "test_string", + BiometricSourceType.FINGERPRINT, + ) + + val status = authenticationStatus as HelpFingerprintAuthenticationStatus + assertThat(status.msgId).isEqualTo(1) + assertThat(status.msg).isEqualTo("test_string") + } + + @Test + fun onFingerprintAcquired_acquiredAuthenticationStatus() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAcquired( + BiometricSourceType.FINGERPRINT, + 5, + ) + + val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus + assertThat(status.acquiredInfo).isEqualTo(5) + } + + @Test + fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() = + testScope.runTest { + val authenticationStatus by collectLastValue(underTest.authenticationStatus) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + updateMonitorCallback.value.onBiometricAuthenticated( + 0, + BiometricSourceType.FACE, + true, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricAuthFailed( + BiometricSourceType.FACE, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricHelp( + 1, + "test_string", + BiometricSourceType.FACE, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricAcquired( + BiometricSourceType.FACE, + 5, + ) + assertThat(authenticationStatus).isNull() + + updateMonitorCallback.value.onBiometricError( + 1, + "test_string", + BiometricSourceType.FACE, + ) + assertThat(authenticationStatus).isNull() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index f541815d2711..ba7d3490e56d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -31,11 +31,14 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController +import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel +import com.android.systemui.keyguard.shared.model.ScreenModel +import com.android.systemui.keyguard.shared.model.ScreenState import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -71,6 +74,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var screenLifecycle: ScreenLifecycle @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var dozeTransitionListener: DozeTransitionListener @Mock private lateinit var authController: AuthController @@ -92,6 +96,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { KeyguardRepositoryImpl( statusBarStateController, wakefulnessLifecycle, + screenLifecycle, biometricUnlockController, keyguardStateController, keyguardBypassController, @@ -160,6 +165,16 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun dozeTimeTick() = + testScope.runTest { + var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick) + underTest.dozeTimeTick() + runCurrent() + + assertThat(dozeTimeTickValue()).isNull() + } + + @Test fun isKeyguardShowing() = testScope.runTest { whenever(keyguardStateController.isShowing).thenReturn(false) @@ -371,6 +386,48 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun screenModel() = + testScope.runTest { + val values = mutableListOf<ScreenModel>() + val job = underTest.screenModel.onEach(values::add).launchIn(this) + + runCurrent() + val captor = argumentCaptor<ScreenLifecycle.Observer>() + verify(screenLifecycle).addObserver(captor.capture()) + + whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON) + captor.value.onScreenTurningOn() + runCurrent() + + whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON) + captor.value.onScreenTurnedOn() + runCurrent() + + whenever(screenLifecycle.getScreenState()) + .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF) + captor.value.onScreenTurningOff() + runCurrent() + + whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF) + captor.value.onScreenTurnedOff() + runCurrent() + + assertThat(values.map { it.state }) + .isEqualTo( + listOf( + // Initial value will be OFF + ScreenState.SCREEN_OFF, + ScreenState.SCREEN_TURNING_ON, + ScreenState.SCREEN_ON, + ScreenState.SCREEN_TURNING_OFF, + ScreenState.SCREEN_OFF, + ) + ) + + job.cancel() + } + + @Test fun isUdfpsSupported() = testScope.runTest { whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true) 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/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 3e81cd336824..ced0a213ca97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -38,10 +38,9 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository -import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository -import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -121,8 +120,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { mock(KeyguardStateController::class.java), bouncerRepository, mock(BiometricSettingsRepository::class.java), - FakeDeviceEntryFingerprintAuthRepository(), FakeSystemClock(), + keyguardUpdateMonitor, ), keyguardTransitionInteractor, featureFlags, @@ -160,7 +159,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { underTest.onDeviceLifted() - val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus + val outputValue = authenticationStatus()!! as ErrorFaceAuthenticationStatus assertThat(outputValue.msgId) .isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT) assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable") 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/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt index d8e418a7815c..b13cb72dc944 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt @@ -26,6 +26,7 @@ import android.view.ViewRootImpl import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq +import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository import org.junit.Before import org.junit.Rule import org.junit.Test @@ -56,6 +57,7 @@ class WallpaperControllerTest : SysuiTestCase() { private lateinit var viewRootImpl: ViewRootImpl @Mock private lateinit var windowToken: IBinder + private val wallpaperRepository = FakeWallpaperRepository() @JvmField @Rule @@ -69,7 +71,7 @@ class WallpaperControllerTest : SysuiTestCase() { `when`(root.windowToken).thenReturn(windowToken) `when`(root.isAttachedToWindow).thenReturn(true) - wallaperController = WallpaperController(wallpaperManager) + wallaperController = WallpaperController(wallpaperManager, wallpaperRepository) wallaperController.rootView = root } @@ -90,9 +92,9 @@ class WallpaperControllerTest : SysuiTestCase() { @Test fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() { - wallaperController.onWallpaperInfoUpdated(createWallpaperInfo( + wallpaperRepository.wallpaperInfo.value = createWallpaperInfo( useDefaultTransition = false - )) + ) wallaperController.setUnfoldTransitionZoom(0.5f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt index 6fc36b08250b..fe5024fdc0a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt @@ -16,9 +16,11 @@ package com.android.systemui.wallpapers.data.repository +import android.app.WallpaperInfo import kotlinx.coroutines.flow.MutableStateFlow /** Fake implementation of the wallpaper repository. */ class FakeWallpaperRepository : WallpaperRepository { + override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null) override val wallpaperSupportsAmbientMode = MutableStateFlow(false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt index 132b9b4a02e1..f8b096a7579b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt @@ -65,6 +65,171 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { } @Test + fun wallpaperInfo_nullInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null) + + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + assertThat(latest).isNull() + } + + @Test + fun wallpaperInfo_hasInfoFromManager() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP) + + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + assertThat(latest).isEqualTo(UNSUPPORTED_WP) + } + + @Test + fun wallpaperInfo_initialValueIsFetched() = + testScope.runTest { + whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id)) + .thenReturn(SUPPORTED_WP) + userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP)) + userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP) + + // WHEN the repo initially starts up (underTest is lazy), then it fetches the current + // value for the wallpaper + assertThat(underTest.wallpaperInfo.value).isEqualTo(SUPPORTED_WP) + } + + @Test + fun wallpaperInfo_updatesOnUserChanged() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0) + val user3Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp) + + val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0) + val user4Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp) + + userRepository.setUserInfos(listOf(user3, user4)) + + // WHEN user3 is selected + userRepository.setSelectedUserInfo(user3) + + // THEN user3's wallpaper is used + assertThat(latest).isEqualTo(user3Wp) + + // WHEN the user is switched to user4 + userRepository.setSelectedUserInfo(user4) + + // THEN user4's wallpaper is used + assertThat(latest).isEqualTo(user4Wp) + } + + @Test + fun wallpaperInfo_doesNotUpdateOnUserChanging() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0) + val user3Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp) + + val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0) + val user4Wp = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp) + + userRepository.setUserInfos(listOf(user3, user4)) + + // WHEN user3 is selected + userRepository.setSelectedUserInfo(user3) + + // THEN user3's wallpaper is used + assertThat(latest).isEqualTo(user3Wp) + + // WHEN the user has started switching to user4 but hasn't finished yet + userRepository.selectedUser.value = + SelectedUserModel(user4, SelectionStatus.SELECTION_IN_PROGRESS) + + // THEN the wallpaper still matches user3 + assertThat(latest).isEqualTo(user3Wp) + } + + @Test + fun wallpaperInfo_updatesOnIntent() = + testScope.runTest { + val latest by collectLastValue(underTest.wallpaperInfo) + + val wp1 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1) + + assertThat(latest).isEqualTo(wp1) + + // WHEN the info is new and a broadcast is sent + val wp2 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp2) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + // THEN the flow updates + assertThat(latest).isEqualTo(wp2) + } + + @Test + fun wallpaperInfo_wallpaperNotSupported_alwaysNull() = + testScope.runTest { + whenever(wallpaperManager.isWallpaperSupported).thenReturn(false) + + val latest by collectLastValue(underTest.wallpaperInfo) + assertThat(latest).isNull() + + // Even WHEN there *is* current wallpaper + val wp1 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + // THEN the value is still null because wallpaper isn't supported + assertThat(latest).isNull() + } + + @Test + fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() = + testScope.runTest { + context.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_dozeSupportsAodWallpaper, + false + ) + + val latest by collectLastValue(underTest.wallpaperInfo) + assertThat(latest).isNull() + + // Even WHEN there *is* current wallpaper + val wp1 = mock<WallpaperInfo>() + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_WALLPAPER_CHANGED), + ) + + // THEN the value is still null because wallpaper isn't supported + assertThat(latest).isNull() + } + + @Test fun wallpaperSupportsAmbientMode_nullInfo_false() = testScope.runTest { val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) @@ -190,14 +355,12 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - val info: WallpaperInfo = mock() - whenever(info.supportsAmbientMode()).thenReturn(false) - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(info) + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP) assertThat(latest).isFalse() // WHEN the info now supports ambient mode and a broadcast is sent - whenever(info.supportsAmbientMode()).thenReturn(true) + whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP) fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( context, Intent(Intent.ACTION_WALLPAPER_CHANGED), diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 4839eeba2124..17bb73b94ac2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -92,7 +92,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -305,6 +305,7 @@ public class BubblesTest extends SysuiTestCase { private TestableLooper mTestableLooper; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private UserHandle mUser0; @@ -423,7 +424,7 @@ public class BubblesTest extends SysuiTestCase { mCommonNotifCollection, mNotifPipeline, mSysUiState, - mock(FeatureFlags.class), + mFeatureFlags, mNotifPipelineFlags, syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback); @@ -432,7 +433,8 @@ public class BubblesTest extends SysuiTestCase { mNotificationTestHelper = new NotificationTestHelper( mContext, mDependency, - TestableLooper.get(this)); + TestableLooper.get(this), + mFeatureFlags); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt index b94f816e1ca4..36fa7e65d8ec 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt @@ -62,6 +62,32 @@ class FakeFeatureFlags : FeatureFlags { } } + /** + * Set the given flag's default value if no other value has been set. + * + * REMINDER: You should always test your code with your flag in both configurations, so + * generally you should be setting a particular value. This method should be reserved for + * situations where the flag needs to be read (e.g. in the class constructor), but its + * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use + * this method than to hard-code `false` or `true` because then at least if you're wrong, + * and the flag value *does* matter, you'll notice when the flag is flipped and tests + * start failing. + */ + fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + + /** + * Set the given flag's default value if no other value has been set. + * + * REMINDER: You should always test your code with your flag in both configurations, so + * generally you should be setting a particular value. This method should be reserved for + * situations where the flag needs to be read (e.g. in the class constructor), but its + * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use + * this method than to hard-code `false` or `true` because then at least if you're wrong, + * and the flag value *does* matter, you'll notice when the flag is flipped and tests + * start failing. + */ + fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + private fun notifyFlagChanged(flag: Flag<*>) { flagListeners[flag.id]?.let { listeners -> listeners.forEach { listener -> diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index 2715aaa82253..548169e6cccd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.FaceAuthUiEvent -import com.android.systemui.keyguard.shared.model.AuthenticationStatus -import com.android.systemui.keyguard.shared.model.DetectionStatus +import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -29,16 +29,16 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { override val isAuthenticated = MutableStateFlow(false) override val canRunFaceAuth = MutableStateFlow(false) - private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null) - override val authenticationStatus: Flow<AuthenticationStatus> = + private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null) + override val authenticationStatus: Flow<FaceAuthenticationStatus> = _authenticationStatus.filterNotNull() - fun setAuthenticationStatus(status: AuthenticationStatus) { + fun setAuthenticationStatus(status: FaceAuthenticationStatus) { _authenticationStatus.value = status } - private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null) - override val detectionStatus: Flow<DetectionStatus> + private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null) + override val detectionStatus: Flow<FaceDetectionStatus> get() = _detectionStatus.filterNotNull() - fun setDetectionStatus(status: DetectionStatus) { + fun setDetectionStatus(status: FaceDetectionStatus) { _detectionStatus.value = status } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt index 4bfd3d64c98e..38791caf5bfc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -17,32 +17,38 @@ package com.android.systemui.keyguard.data.repository +import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository { private val _isLockedOut = MutableStateFlow(false) override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow() - - private val _isRunning = MutableStateFlow(false) - override val isRunning: Flow<Boolean> - get() = _isRunning - - private var fpSensorType = MutableStateFlow<BiometricType?>(null) - override val availableFpSensorType: Flow<BiometricType?> - get() = fpSensorType - fun setLockedOut(lockedOut: Boolean) { _isLockedOut.value = lockedOut } + private val _isRunning = MutableStateFlow(false) + override val isRunning: Flow<Boolean> + get() = _isRunning fun setIsRunning(value: Boolean) { _isRunning.value = value } + private var fpSensorType = MutableStateFlow<BiometricType?>(null) + override val availableFpSensorType: Flow<BiometricType?> + get() = fpSensorType fun setAvailableFpSensorType(value: BiometricType?) { fpSensorType.value = value } + + private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null) + override val authenticationStatus: Flow<FingerprintAuthenticationStatus> + get() = _authenticationStatus.filterNotNull() + fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) { + _authenticationStatus.value = status + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index b9d098fe2851..8428566270de 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -22,11 +22,14 @@ import com.android.systemui.common.shared.model.Position import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeTransitionModel +import com.android.systemui.keyguard.shared.model.ScreenModel +import com.android.systemui.keyguard.shared.model.ScreenState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -56,6 +59,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: StateFlow<Boolean> = _isDozing + private val _dozeTimeTick = MutableSharedFlow<Unit>() + override val dozeTimeTick = _dozeTimeTick + private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow() @@ -86,6 +92,9 @@ class FakeKeyguardRepository : KeyguardRepository { ) override val wakefulness = _wakefulnessModel + private val _screenModel = MutableStateFlow(ScreenModel(ScreenState.SCREEN_OFF)) + override val screenModel = _screenModel + private val _isUdfpsSupported = MutableStateFlow(false) private val _isKeyguardGoingAway = MutableStateFlow(false) @@ -147,6 +156,10 @@ class FakeKeyguardRepository : KeyguardRepository { _isDozing.value = isDozing } + override fun dozeTimeTick() { + _dozeTimeTick.tryEmit(Unit) + } + override fun setLastDozeTapToWakePosition(position: Point) { _lastDozeTapToWakePosition.value = position } diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 7c3a75f256f9..5a15f17a0922 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -49,6 +49,7 @@ public class MasterClearReceiver extends BroadcastReceiver { private static final String TAG = "MasterClear"; private boolean mWipeExternalStorage; private boolean mWipeEsims; + private boolean mShowWipeProgress = true; @Override public void onReceive(final Context context, final Intent intent) { @@ -77,8 +78,12 @@ public class MasterClearReceiver extends BroadcastReceiver { return; } - final boolean shutdown = intent.getBooleanExtra("shutdown", false); final String reason = intent.getStringExtra(Intent.EXTRA_REASON); + + // Factory reset dialog has its own UI for the reset process, so suppress ours if indicated. + mShowWipeProgress = intent.getBooleanExtra(Intent.EXTRA_SHOW_WIPE_PROGRESS, true); + + final boolean shutdown = intent.getBooleanExtra("shutdown", false); mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false); mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false); final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false) @@ -190,15 +195,19 @@ public class MasterClearReceiver extends BroadcastReceiver { public WipeDataTask(Context context, Thread chainedTask) { mContext = context; mChainedTask = chainedTask; - mProgressDialog = new ProgressDialog(context, R.style.Theme_DeviceDefault_System); + mProgressDialog = mShowWipeProgress + ? new ProgressDialog(context, R.style.Theme_DeviceDefault_System) + : null; } @Override protected void onPreExecute() { - mProgressDialog.setIndeterminate(true); - mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing)); - mProgressDialog.show(); + if (mProgressDialog != null) { + mProgressDialog.setIndeterminate(true); + mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing)); + mProgressDialog.show(); + } } @Override @@ -214,7 +223,9 @@ public class MasterClearReceiver extends BroadcastReceiver { @Override protected void onPostExecute(Void result) { - mProgressDialog.dismiss(); + if (mProgressDialog != null && mProgressDialog.isShowing()) { + mProgressDialog.dismiss(); + } mChainedTask.start(); } 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 13fd2f261652..134b041cb242 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -110,6 +110,7 @@ import android.app.backup.IBackupManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.DataLoaderType; @@ -119,7 +120,6 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; -import android.content.pm.ResolveInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; @@ -184,7 +184,9 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; @@ -207,6 +209,7 @@ import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -1077,8 +1080,6 @@ final class InstallPackageHelper { final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0); final boolean isRollback = request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK; - final boolean extractProfile = - ((installFlags & PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES) == 0); @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; if (request.isInstallMove()) { // moving a complete application; perform an initial scan on the new install location @@ -1114,9 +1115,7 @@ final class InstallPackageHelper { @ParsingPackageUtils.ParseFlags final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY | ParsingPackageUtils.PARSE_ENFORCE_CODE - | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0) - | (extractProfile - ? ParsingPackageUtils.PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK : 0); + | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); final ParsedPackage parsedPackage; @@ -3940,23 +3939,6 @@ final class InstallPackageHelper { } } - // If this is a system app we hadn't seen before, and this is a first boot or OTA, - // we need to unstop it if it doesn't have a launcher entry. - if (mPm.mShouldStopSystemPackagesByDefault && scanResult.mRequest.mPkgSetting == null - && ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) - && ((scanFlags & SCAN_AS_SYSTEM) != 0)) { - final Intent launcherIntent = new Intent(Intent.ACTION_MAIN); - launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); - launcherIntent.setPackage(parsedPackage.getPackageName()); - final List<ResolveInfo> launcherActivities = - mPm.snapshotComputer().queryIntentActivitiesInternal(launcherIntent, null, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0); - if (launcherActivities.isEmpty()) { - scanResult.mPkgSetting.setStopped(false, 0); - } - } - if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) { if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) { // Continue monitoring loading progress of active incremental packages @@ -4329,6 +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 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,8 +4321,9 @@ final class InstallPackageHelper { && !parsedPackage.isOverlayIsStatic() ) { String packageName = parsedPackage.getPackageName(); - if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName) - && !"android".contentEquals(packageName)) { + if (!"android".contentEquals(packageName) + && !mPm.mInitialNonStoppedSystemPackages.contains(packageName) + && hasLauncherEntry(parsedPackage)) { scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP; } } @@ -4348,6 +4333,26 @@ final class InstallPackageHelper { return new Pair<>(scanResult, shouldHideSystemApp); } + private static boolean hasLauncherEntry(ParsedPackage parsedPackage) { + final HashSet<String> categories = new HashSet<>(); + categories.add(Intent.CATEGORY_LAUNCHER); + final List<ParsedActivity> activities = parsedPackage.getActivities(); + for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) { + 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) { + return true; + } + } + } + return false; + } + /** * Returns if forced apk verification can be skipped for the whole package, including splits. */ diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 2fb1ed1bbc57..8d64bd9fb66a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3456,10 +3456,6 @@ class PackageManagerShellCommand extends ShellCommand { sessionParams.installFlags |= PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK; break; - case "--no-profile": - sessionParams.installFlags |= - PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES; - break; default: throw new IllegalArgumentException("Unknown option " + opt); } @@ -4348,7 +4344,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]"); pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]"); pw.println(" [--preload] [--instant] [--full] [--dont-kill]"); - pw.println(" [--enable-rollback] [--no-profile]"); + pw.println(" [--enable-rollback]"); pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); pw.println(" [--apex] [--force-non-staged] [--staged-ready-timeout TIMEOUT]"); pw.println(" [PATH [SPLIT...]|-]"); @@ -4381,7 +4377,6 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --apex: install an .apex file, not an .apk"); pw.println(" --force-non-staged: force the installation to run under a non-staged"); pw.println(" session, which may complete without requiring a reboot"); - pw.println(" --no-profile: don't extract the profiles from the apk"); pw.println(" --staged-ready-timeout: By default, staged sessions wait " + DEFAULT_STAGED_READY_TIMEOUT_MS); pw.println(" milliseconds for pre-reboot verification to complete when"); @@ -4403,7 +4398,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]"); pw.println(" [--preload] [--instant] [--full] [--dont-kill]"); pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]"); - pw.println(" [--multi-package] [--staged] [--no-profile] [--update-ownership]"); + pw.println(" [--multi-package] [--staged] [--update-ownership]"); pw.println(" Like \"install\", but starts an install session. Use \"install-write\""); pw.println(" to push data into the session, and \"install-commit\" to finish."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 9b926973945c..494709c17e06 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -1195,8 +1195,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } public PackageSetting setLoadingProgress(float progress) { - mLoadingProgress = progress; - onChanged(); + // To prevent race conditions, we don't allow progress to ever go down + if (mLoadingProgress < progress) { + mLoadingProgress = progress; + onChanged(); + } return this; } 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/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index d737b1c6bfa6..e2cb87e72c7a 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -50,7 +50,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.PackageLite; @@ -74,7 +73,6 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.ext.SdkExtensions; -import android.os.incremental.IncrementalManager; import android.permission.PermissionManager; import android.text.TextUtils; import android.util.ArrayMap; @@ -242,11 +240,6 @@ public class ParsingPackageUtils { public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; public static final int PARSE_APK_IN_APEX = 1 << 9; - /** - * This flag is to determine whether to extract the baseline profiles from the apk or not. - */ - public static final int PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK = 1 << 10; - public static final int PARSE_CHATTY = 1 << 31; /** The total maximum number of activities, services, providers and activity-aliases */ @@ -258,16 +251,14 @@ public class ParsingPackageUtils { private static final int MAX_PERMISSION_NAME_LENGTH = 512; @IntDef(flag = true, prefix = { "PARSE_" }, value = { - PARSE_APK_IN_APEX, PARSE_CHATTY, PARSE_COLLECT_CERTIFICATES, PARSE_ENFORCE_CODE, PARSE_EXTERNAL_STORAGE, - PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK, - PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY, PARSE_IGNORE_PROCESSES, PARSE_IS_SYSTEM_DIR, PARSE_MUST_BE_APK, + PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY, }) @Retention(RetentionPolicy.SOURCE) public @interface ParseFlags {} @@ -568,26 +559,6 @@ public class ParsingPackageUtils { pkg.setSigningDetails(SigningDetails.UNKNOWN); } - // 1. The apkFile is an apk file - // 2. The flags include PARSE_EXTRACT_PROFILE_FROM_APK - // 3. The apk patch is NOT an incremental path - // 4. If the .dm file exists in the current apk directory, it means the caller - // prepares the .dm file. Don't extract the profiles from the apk again. - if (ApkLiteParseUtils.isApkFile(apkFile) - && (flags & PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK) != 0 - && !IncrementalManager.isIncrementalPath(apkPath) - && DexMetadataHelper.findDexMetadataForFile(apkFile) == null) { - // Extract the baseline profiles from the apk if the profiles exist in the assets - // directory in the apk. - boolean extractedResult = - DexMetadataHelper.extractBaselineProfilesToDexMetadataFileFromApk(assets, - apkPath); - - if (DEBUG_JAR) { - Slog.d(TAG, "Extract profiles " + (extractedResult ? "success" : "fail")); - } - } - return input.success(pkg); } catch (Exception e) { return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 6e441bfb534e..7bbe8781e434 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -6545,6 +6545,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private class HdmiVideoExtconUEventObserver extends ExtconStateObserver<Boolean> { private static final String HDMI_EXIST = "HDMI=1"; + private static final String DP_EXIST = "DP=1"; private static final String NAME = "hdmi"; private boolean init(ExtconInfo hdmi) { @@ -6575,7 +6576,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { public Boolean parseState(ExtconInfo extconIfno, String state) { // extcon event state changes from kernel4.9 // new state will be like STATE=HDMI=1 - return state.contains(HDMI_EXIST); + // or like STATE=DP=1 for newer kernel + return state.contains(HDMI_EXIST) || state.contains(DP_EXIST); } } diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 5ae697315ed1..95296210be80 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -184,13 +184,7 @@ public class FileIntegrityService extends SystemService { } private void loadAllCertificates() { - // A better alternative to load certificates would be to read from .fs-verity kernel - // keyring, which fsverity_init loads to during earlier boot time from the same sources - // below. But since the read operation from keyring is not provided in kernel, we need to - // duplicate the same loading logic here. - // Load certificates trusted by the device manufacturer. - // NB: Directories need to be synced with system/security/fsverity_init/fsverity_init.cpp. final String relativeDir = "etc/security/fsverity"; loadCertificatesFromDirectory(Environment.getRootDirectory().toPath() .resolve(relativeDir)); 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/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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e261916dffed..349d11568e71 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6404,9 +6404,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Don't do recursive work. return; } - mInEnsureActivitiesVisible = true; mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate(); try { + mInEnsureActivitiesVisible = true; forAllRootTasks(rootTask -> { rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows, notifyClients); diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 7a201a77c966..e945bc1babd9 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -294,16 +294,15 @@ final class LetterboxConfiguration { @NonNull private final SynchedDeviceConfig mDeviceConfig; LetterboxConfiguration(@NonNull final Context systemUiContext) { - this(systemUiContext, - new LetterboxConfigurationPersister(systemUiContext, - () -> readLetterboxHorizontalReachabilityPositionFromConfig( - systemUiContext, /* forBookMode */ false), - () -> readLetterboxVerticalReachabilityPositionFromConfig( - systemUiContext, /* forTabletopMode */ false), - () -> readLetterboxHorizontalReachabilityPositionFromConfig( - systemUiContext, /* forBookMode */ true), - () -> readLetterboxVerticalReachabilityPositionFromConfig( - systemUiContext, /* forTabletopMode */ true))); + this(systemUiContext, new LetterboxConfigurationPersister( + () -> readLetterboxHorizontalReachabilityPositionFromConfig( + systemUiContext, /* forBookMode */ false), + () -> readLetterboxVerticalReachabilityPositionFromConfig( + systemUiContext, /* forTabletopMode */ false), + () -> readLetterboxHorizontalReachabilityPositionFromConfig( + systemUiContext, /* forBookMode */ true), + () -> readLetterboxVerticalReachabilityPositionFromConfig( + systemUiContext, /* forTabletopMode */ true))); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java index 756339701590..38aa903e3954 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java +++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java @@ -23,7 +23,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.Context; import android.os.Environment; import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; @@ -53,10 +52,8 @@ class LetterboxConfigurationPersister { private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM; - @VisibleForTesting - static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config"; + private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config"; - private final Context mContext; private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier; private final Supplier<Integer> mDefaultVerticalReachabilitySupplier; private final Supplier<Integer> mDefaultBookModeReachabilitySupplier; @@ -97,36 +94,32 @@ class LetterboxConfigurationPersister { @NonNull private final PersisterQueue mPersisterQueue; - LetterboxConfigurationPersister(Context systemUiContext, - Supplier<Integer> defaultHorizontalReachabilitySupplier, - Supplier<Integer> defaultVerticalReachabilitySupplier, - Supplier<Integer> defaultBookModeReachabilitySupplier, - Supplier<Integer> defaultTabletopModeReachabilitySupplier) { - this(systemUiContext, defaultHorizontalReachabilitySupplier, - defaultVerticalReachabilitySupplier, - defaultBookModeReachabilitySupplier, - defaultTabletopModeReachabilitySupplier, + LetterboxConfigurationPersister( + @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, + @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, + @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, + @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) { + this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier, + defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier, Environment.getDataSystemDirectory(), new PersisterQueue(), - /* completionCallback */ null); + /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME); } @VisibleForTesting - LetterboxConfigurationPersister(Context systemUiContext, - Supplier<Integer> defaultHorizontalReachabilitySupplier, - Supplier<Integer> defaultVerticalReachabilitySupplier, - Supplier<Integer> defaultBookModeReachabilitySupplier, - Supplier<Integer> defaultTabletopModeReachabilitySupplier, - File configFolder, - PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) { - mContext = systemUiContext.createDeviceProtectedStorageContext(); + LetterboxConfigurationPersister( + @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, + @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, + @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, + @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier, + @NonNull File configFolder, @NonNull PersisterQueue persisterQueue, + @Nullable Consumer<String> completionCallback, + @NonNull String letterboxConfigurationFileName) { mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier; mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier; - mDefaultBookModeReachabilitySupplier = - defaultBookModeReachabilitySupplier; - mDefaultTabletopModeReachabilitySupplier = - defaultTabletopModeReachabilitySupplier; + mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier; + mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier; mCompletionCallback = completionCallback; - final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME); + final File prefFiles = new File(configFolder, letterboxConfigurationFileName); mConfigurationFile = new AtomicFile(prefFiles); mPersisterQueue = persisterQueue; runWithDiskReadsThreadPolicy(this::readCurrentConfiguration); 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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6e46f3f7e6f3..2f0c303ec839 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1849,9 +1849,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Don't do recursive work. return; } - + mTaskSupervisor.beginActivityVisibilityUpdate(); try { - mTaskSupervisor.beginActivityVisibilityUpdate(); // First the front root tasks. In case any are not fullscreen and are in front of home. for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { final DisplayContent display = getChildAt(displayNdx); diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index c914fa10687f..fe3094e2edf0 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -293,26 +293,7 @@ public class SafeActivityOptions { throw new SecurityException(msg); } // Check if the caller is allowed to launch on the specified display area. - final WindowContainerToken daToken = options.getLaunchTaskDisplayArea(); - TaskDisplayArea taskDisplayArea = daToken != null - ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null; - - // If we do not have a task display area token, check if the launch task display area - // feature id is specified. - if (taskDisplayArea == null) { - final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId(); - if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) { - final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY - ? DEFAULT_DISPLAY : options.getLaunchDisplayId(); - final DisplayContent dc = supervisor.mRootWindowContainer - .getDisplayContent(launchDisplayId); - if (dc != null) { - taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda -> - tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null); - } - } - } - + final TaskDisplayArea taskDisplayArea = getLaunchTaskDisplayArea(options, supervisor); if (aInfo != null && taskDisplayArea != null && !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid, taskDisplayArea, aInfo)) { @@ -428,6 +409,32 @@ public class SafeActivityOptions { } } + @VisibleForTesting + TaskDisplayArea getLaunchTaskDisplayArea(ActivityOptions options, + ActivityTaskSupervisor supervisor) { + final WindowContainerToken daToken = options.getLaunchTaskDisplayArea(); + TaskDisplayArea taskDisplayArea = daToken != null + ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null; + if (taskDisplayArea != null) { + return taskDisplayArea; + } + + // If we do not have a task display area token, check if the launch task display area + // feature id is specified. + final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId(); + if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) { + final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY + ? DEFAULT_DISPLAY : options.getLaunchDisplayId(); + final DisplayContent dc = supervisor.mRootWindowContainer + .getDisplayContent(launchDisplayId); + if (dc != null) { + taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda -> + tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null); + } + } + return taskDisplayArea; + } + private boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) { if (atmService.mActiveVoiceInteractionServiceComponent == null) { return false; 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/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/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 72c5333e0a02..410ae35aa790 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -328,7 +328,7 @@ public class BroadcastQueueTest { eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any()); mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); - mConstants.TIMEOUT = 100; + mConstants.TIMEOUT = 200; mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500; 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/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java index 3ac59e9f0efe..f20633342759 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java @@ -33,6 +33,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; import android.os.Looper; +import android.os.Process; +import android.util.SparseArray; import org.junit.After; import org.junit.Before; @@ -244,6 +246,54 @@ public class PackageMonitorCallbackHelperTest { verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any()); } + @Test + public void testRegisterPackageMonitorCallbackInAllowList_callbackShouldCalled() + throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + SparseArray<int[]> broadcastAllowList = new SparseArray<>(); + broadcastAllowList.put(0, new int[] {Binder.getCallingUid()}); + + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */, + Binder.getCallingUid()); + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, + null /* instantUserIds */, broadcastAllowList); + + verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any()); + } + + @Test + public void testRegisterPackageMonitorCallbackNotInAllowList_callbackShouldNotCalled() + throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + SparseArray<int[]> broadcastAllowList = new SparseArray<>(); + broadcastAllowList.put(0, new int[] {12345}); + + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */, + Binder.getCallingUid()); + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, + null /* instantUserIds */, broadcastAllowList); + + verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any()); + } + + @Test + public void testRegisterPackageMonitorCallbackNotInAllowListSystemUid_callbackShouldCalled() + throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + SparseArray<int[]> broadcastAllowList = new SparseArray<>(); + broadcastAllowList.put(0, new int[] {12345}); + + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */, + Process.SYSTEM_UID); + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, + null /* instantUserIds */, broadcastAllowList); + + verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any()); + } + private IRemoteCallback createMockPackageMonitorCallback() { return spy(new IRemoteCallback.Stub() { @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java deleted file mode 100644 index 0f2c27c83791..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.power; - -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.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.ActivityManagerInternal; -import android.attention.AttentionManagerInternal; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.res.Resources; -import android.hardware.SensorManager; -import android.hardware.devicestate.DeviceStateManager; -import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; -import android.hardware.display.AmbientDisplayConfiguration; -import android.hardware.display.DisplayManagerInternal; -import android.os.BatteryManagerInternal; -import android.os.Handler; -import android.os.Looper; -import android.os.PowerManager; -import android.os.PowerSaveState; -import android.os.test.TestLooper; -import android.provider.Settings; -import android.service.dreams.DreamManagerInternal; -import android.test.mock.MockContentResolver; -import android.view.Display; -import android.view.DisplayInfo; - -import androidx.test.InstrumentationRegistry; - -import com.android.internal.app.IBatteryStats; -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.LocalServices; -import com.android.server.SystemService; -import com.android.server.lights.LightsManager; -import com.android.server.policy.WindowManagerPolicy; -import com.android.server.power.PowerManagerService.BatteryReceiver; -import com.android.server.power.PowerManagerService.Injector; -import com.android.server.power.PowerManagerService.NativeWrapper; -import com.android.server.power.PowerManagerService.UserSwitchedReceiver; -import com.android.server.power.batterysaver.BatterySaverController; -import com.android.server.power.batterysaver.BatterySaverPolicy; -import com.android.server.power.batterysaver.BatterySaverStateMachine; -import com.android.server.power.batterysaver.BatterySavingStats; -import com.android.server.testutils.OffsettableClock; - -import java.util.concurrent.Executor; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link com.android.server.power.PowerManagerService}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:PowerManagerServiceMockingTest - */ -public class PowerManagerServiceMockingTest { - private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent"; - private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason"; - - private static final float BRIGHTNESS_FACTOR = 0.7f; - private static final boolean BATTERY_SAVER_ENABLED = true; - - @Mock private BatterySaverController mBatterySaverControllerMock; - @Mock private BatterySaverPolicy mBatterySaverPolicyMock; - @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock; - @Mock private LightsManager mLightsManagerMock; - @Mock private DisplayManagerInternal mDisplayManagerInternalMock; - @Mock private BatteryManagerInternal mBatteryManagerInternalMock; - @Mock private ActivityManagerInternal mActivityManagerInternalMock; - @Mock private AttentionManagerInternal mAttentionManagerInternalMock; - @Mock private DreamManagerInternal mDreamManagerInternalMock; - @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock; - @Mock private Notifier mNotifierMock; - @Mock private WirelessChargerDetector mWirelessChargerDetectorMock; - @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock; - @Mock private SystemPropertiesWrapper mSystemPropertiesMock; - @Mock private DeviceStateManager mDeviceStateManagerMock; - - @Mock - private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; - - private PowerManagerService mService; - private PowerSaveState mPowerSaveState; - private ContextWrapper mContextSpy; - private BatteryReceiver mBatteryReceiver; - private UserSwitchedReceiver mUserSwitchedReceiver; - private Resources mResourcesSpy; - private OffsettableClock mClock; - private TestLooper mTestLooper; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - FakeSettingsProvider.clearSettingsProvider(); - - mPowerSaveState = new PowerSaveState.Builder() - .setBatterySaverEnabled(BATTERY_SAVER_ENABLED) - .setBrightnessFactor(BRIGHTNESS_FACTOR) - .build(); - when(mBatterySaverPolicyMock.getBatterySaverPolicy( - eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS))) - .thenReturn(mPowerSaveState); - when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false); - when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false); - when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean())) - .thenReturn(true); - when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); - when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); - - addLocalServiceMock(LightsManager.class, mLightsManagerMock); - addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock); - addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock); - addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); - addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock); - addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock); - - mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); - mResourcesSpy = spy(mContextSpy.getResources()); - when(mContextSpy.getResources()).thenReturn(mResourcesSpy); - - MockContentResolver cr = new MockContentResolver(mContextSpy); - cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(mContextSpy.getContentResolver()).thenReturn(cr); - - when(mContextSpy.getSystemService(DeviceStateManager.class)) - .thenReturn(mDeviceStateManagerMock); - - Settings.Global.putInt(mContextSpy.getContentResolver(), - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); - - mClock = new OffsettableClock.Stopped(); - mTestLooper = new TestLooper(mClock::now); - } - - private PowerManagerService createService() { - mService = new PowerManagerService(mContextSpy, new Injector() { - @Override - Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, - SuspendBlocker suspendBlocker, WindowManagerPolicy policy, - FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector, - Executor executor) { - return mNotifierMock; - } - - @Override - SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) { - return super.createSuspendBlocker(service, name); - } - - @Override - BatterySaverPolicy createBatterySaverPolicy( - Object lock, Context context, BatterySavingStats batterySavingStats) { - return mBatterySaverPolicyMock; - } - - @Override - BatterySaverController createBatterySaverController( - Object lock, Context context, BatterySaverPolicy batterySaverPolicy, - BatterySavingStats batterySavingStats) { - return mBatterySaverControllerMock; - } - - @Override - BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context, - BatterySaverController batterySaverController) { - return mBatterySaverStateMachineMock; - } - - @Override - NativeWrapper createNativeWrapper() { - return mNativeWrapperMock; - } - - @Override - WirelessChargerDetector createWirelessChargerDetector( - SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) { - return mWirelessChargerDetectorMock; - } - - @Override - AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) { - return mAmbientDisplayConfigurationMock; - } - - @Override - InattentiveSleepWarningController createInattentiveSleepWarningController() { - return mInattentiveSleepWarningControllerMock; - } - - @Override - public SystemPropertiesWrapper createSystemPropertiesWrapper() { - return mSystemPropertiesMock; - } - - @Override - PowerManagerService.Clock createClock() { - return new PowerManagerService.Clock() { - @Override - public long uptimeMillis() { - return mClock.now(); - } - - @Override - public long elapsedRealtime() { - return mClock.now(); - } - }; - } - - @Override - Handler createHandler(Looper looper, Handler.Callback callback) { - return new Handler(mTestLooper.getLooper(), callback); - } - - @Override - void invalidateIsInteractiveCaches() { - // Avoids an SELinux failure. - } - }); - return mService; - } - - @After - public void tearDown() throws Exception { - LocalServices.removeServiceForTest(LightsManager.class); - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - LocalServices.removeServiceForTest(BatteryManagerInternal.class); - LocalServices.removeServiceForTest(ActivityManagerInternal.class); - LocalServices.removeServiceForTest(AttentionManagerInternal.class); - LocalServices.removeServiceForTest(DreamManagerInternal.class); - FakeSettingsProvider.clearSettingsProvider(); - } - - /** - * Creates a mock and registers it to {@link LocalServices}. - */ - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - - private void advanceTime(long timeMs) { - mClock.fastForward(timeMs); - mTestLooper.dispatchAll(); - } - - @Test - public void testUserActivityOnDeviceStateChange() { - createService(); - mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); - mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); - - final DisplayInfo info = new DisplayInfo(); - info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; - when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); - - final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor = - ArgumentCaptor.forClass(DeviceStateCallback.class); - verify(mDeviceStateManagerMock).registerCallback(any(), - deviceStateCallbackCaptor.capture()); - - // Advance the time 10001 and verify that the device thinks it has been idle - // for just less than that. - mService.onUserActivity(); - advanceTime(10001); - assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue(); - - // Send a display state change event and advance the clock 10. - final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue(); - deviceStateCallback.onStateChanged(1); - final long timeToAdvance = 10; - advanceTime(timeToAdvance); - - // Ensure that the device has been idle for only 10 (doesn't include the idle time - // before the display state event). - assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue(); - assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse(); - - // Send the same state and ensure that does not trigger an update. - deviceStateCallback.onStateChanged(1); - advanceTime(timeToAdvance); - final long newTime = timeToAdvance * 2; - - assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue(); - assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse(); - } -} diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp index 236f90c0c3e5..938401521624 100644 --- a/services/tests/powerservicetests/Android.bp +++ b/services/tests/powerservicetests/Android.bp @@ -12,9 +12,19 @@ android_test { static_libs: [ "frameworks-base-testutils", + "platform-compat-test-rules", "platform-test-annotations", "services.core", "servicestests-utils", + "testables", + ], + + libs: [ + "android.test.mock", + ], + + defaults: [ + "modules-utils-testable-device-config-defaults", ], platform_apis: true, diff --git a/services/tests/powerservicetests/AndroidManifest.xml b/services/tests/powerservicetests/AndroidManifest.xml index 3ace9d512adc..26d9eec21fb1 100644 --- a/services/tests/powerservicetests/AndroidManifest.xml +++ b/services/tests/powerservicetests/AndroidManifest.xml @@ -17,10 +17,19 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.powerservicetests"> - <!-- - Insert permissions here. eg: - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> - --> + <!-- Permissions --> + <uses-permission android:name="android.permission.DEVICE_POWER"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/> + <uses-permission android:name="android.permission.READ_DREAM_STATE"/> + <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/> + <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/> + <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> + <application android:debuggable="true" android:testOnly="true"> <uses-library android:name="android.test.mock" android:required="true" /> diff --git a/services/tests/powerservicetests/OWNERS b/services/tests/powerservicetests/OWNERS index e740577fae90..aff093057a29 100644 --- a/services/tests/powerservicetests/OWNERS +++ b/services/tests/powerservicetests/OWNERS @@ -2,4 +2,3 @@ include /services/core/java/com/android/server/power/OWNERS -per-file ThermalManagerServiceTest.java=wvw@google.com, xwxw@google.com
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java index d3c0e354838b..d3c0e354838b 100644 --- a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index 2f039654ca7e..2f039654ca7e 100644 --- a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java index fe31b9cbe558..fe31b9cbe558 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index c80ff45e7768..9463b2d9824e 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -60,6 +60,8 @@ import android.content.IntentFilter; import android.content.PermissionChecker; import android.content.res.Resources; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; @@ -160,6 +162,7 @@ public class PowerManagerServiceTest { @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock; @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper; + @Mock private DeviceStateManager mDeviceStateManagerMock; @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); @@ -2710,4 +2713,48 @@ public class PowerManagerServiceTest { verify(mNotifierMock, never()).onUserActivity(anyInt(), anyInt(), anyInt()); } + @Test + public void testUserActivityOnDeviceStateChange() { + when(mContextSpy.getSystemService(DeviceStateManager.class)) + .thenReturn(mDeviceStateManagerMock); + + createService(); + mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); + + final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor = + ArgumentCaptor.forClass(DeviceStateCallback.class); + verify(mDeviceStateManagerMock).registerCallback(any(), + deviceStateCallbackCaptor.capture()); + + // Advance the time 10001 and verify that the device thinks it has been idle + // for just less than that. + mService.onUserActivity(); + advanceTime(10001); + assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue(); + + // Send a display state change event and advance the clock 10. + final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue(); + deviceStateCallback.onStateChanged(1); + final long timeToAdvance = 10; + advanceTime(timeToAdvance); + + // Ensure that the device has been idle for only 10 (doesn't include the idle time + // before the display state event). + assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue(); + assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse(); + + // Send the same state and ensure that does not trigger an update. + deviceStateCallback.onStateChanged(1); + advanceTime(timeToAdvance); + final long newTime = timeToAdvance * 2; + + assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue(); + assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse(); + } + } diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java index fe6cc28f03d3..fe6cc28f03d3 100644 --- a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownThreadTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java index 6041e916ffc0..6041e916ffc0 100644 --- a/services/tests/servicestests/src/com/android/server/power/ShutdownThreadTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java index 7af4b3d87a3a..7af4b3d87a3a 100644 --- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index 27e6ef199fe2..bbbab2129d22 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -66,7 +66,6 @@ import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; @@ -308,7 +307,6 @@ public class WindowMagnificationManagerTest { MagnificationScaleProvider.MAX_SCALE); } - @Ignore("b/278816260: We could refer to b/182561174#comment4 for solution.") @Test public void logTrackingTypingFocus_processScroll_logDuration() { WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager); 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/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java index fe1ea0d99eeb..f6f3f0324f9c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java @@ -22,11 +22,17 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; import android.app.Activity; import android.app.ActivityManager.RunningTaskInfo; @@ -41,6 +47,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.util.Log; import android.util.Rational; import android.view.SurfaceControl; import android.window.TaskOrganizer; @@ -48,7 +55,10 @@ import android.window.TaskOrganizer; import androidx.test.filters.MediumTest; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -166,6 +176,122 @@ public class ActivityOptionsTest { } } + /** + * Tests if any unknown key is being used in the ActivityOptions bundle. If so, please review + * if the newly added bundle should be protected with permissions to avoid malicious attacks. + * + * @see SafeActivityOptionsTest#test_getOptions + */ + @Test + public void testActivityOptionsFromBundle() { + // Spy on a bundle that is generated from a basic ActivityOptions. + final ActivityOptions options = ActivityOptions.makeBasic(); + Bundle bundle = options.toBundle(); + spyOn(bundle); + + // Create a new ActivityOptions from the bundle + new ActivityOptions(bundle); + + // Verify the keys that are being used. + final ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class); + verify(bundle, atLeastOnce()).getString(stringCaptor.capture()); + verify(bundle, atLeastOnce()).getBoolean(stringCaptor.capture()); + verify(bundle, atLeastOnce()).getParcelable(stringCaptor.capture(), any()); + verify(bundle, atLeastOnce()).getInt(stringCaptor.capture(), anyInt()); + verify(bundle, atLeastOnce()).getBinder(stringCaptor.capture()); + verify(bundle, atLeastOnce()).getBundle(stringCaptor.capture()); + final List<String> keys = stringCaptor.getAllValues(); + final List<String> unknownKeys = new ArrayList<>(); + for (String key : keys) { + switch (key) { + case ActivityOptions.KEY_PACKAGE_NAME: + case ActivityOptions.KEY_LAUNCH_BOUNDS: + case ActivityOptions.KEY_ANIM_TYPE: + case ActivityOptions.KEY_ANIM_ENTER_RES_ID: + case ActivityOptions.KEY_ANIM_EXIT_RES_ID: + case ActivityOptions.KEY_ANIM_IN_PLACE_RES_ID: + case ActivityOptions.KEY_ANIM_BACKGROUND_COLOR: + case ActivityOptions.KEY_ANIM_THUMBNAIL: + case ActivityOptions.KEY_ANIM_START_X: + case ActivityOptions.KEY_ANIM_START_Y: + case ActivityOptions.KEY_ANIM_WIDTH: + case ActivityOptions.KEY_ANIM_HEIGHT: + case ActivityOptions.KEY_ANIM_START_LISTENER: + case ActivityOptions.KEY_SPLASH_SCREEN_THEME: + case ActivityOptions.KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE: + case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN: + case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN: + case ActivityOptions.KEY_TRANSIENT_LAUNCH: + case "android:activity.animationFinishedListener": + // KEY_ANIMATION_FINISHED_LISTENER + case "android:activity.animSpecs": // KEY_ANIM_SPECS + case "android:activity.lockTaskMode": // KEY_LOCK_TASK_MODE + case "android:activity.shareIdentity": // KEY_SHARE_IDENTITY + case "android.activity.launchDisplayId": // KEY_LAUNCH_DISPLAY_ID + case "android.activity.callerDisplayId": // KEY_CALLER_DISPLAY_ID + case "android.activity.launchTaskDisplayAreaToken": + // KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN + case "android.activity.launchTaskDisplayAreaFeatureId": + // KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID + case "android.activity.windowingMode": // KEY_LAUNCH_WINDOWING_MODE + case "android.activity.activityType": // KEY_LAUNCH_ACTIVITY_TYPE + case "android.activity.launchTaskId": // KEY_LAUNCH_TASK_ID + case "android.activity.disableStarting": // KEY_DISABLE_STARTING_WINDOW + case "android.activity.pendingIntentLaunchFlags": + // KEY_PENDING_INTENT_LAUNCH_FLAGS + case "android.activity.alwaysOnTop": // KEY_TASK_ALWAYS_ON_TOP + case "android.activity.taskOverlay": // KEY_TASK_OVERLAY + case "android.activity.taskOverlayCanResume": // KEY_TASK_OVERLAY_CAN_RESUME + case "android.activity.avoidMoveToFront": // KEY_AVOID_MOVE_TO_FRONT + case "android.activity.freezeRecentTasksReordering": + // KEY_FREEZE_RECENT_TASKS_REORDERING + case "android:activity.disallowEnterPictureInPictureWhileLaunching": + // KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING + case "android:activity.applyActivityFlagsForBubbles": + // KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES + case "android:activity.applyMultipleTaskFlagForShortcut": + // KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT + case "android:activity.applyNoUserActionFlagForShortcut": + // KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT + case "android:activity.transitionCompleteListener": + // KEY_TRANSITION_COMPLETE_LISTENER + case "android:activity.transitionIsReturning": // KEY_TRANSITION_IS_RETURNING + case "android:activity.sharedElementNames": // KEY_TRANSITION_SHARED_ELEMENTS + case "android:activity.resultData": // KEY_RESULT_DATA + case "android:activity.resultCode": // KEY_RESULT_CODE + case "android:activity.exitCoordinatorIndex": // KEY_EXIT_COORDINATOR_INDEX + case "android.activity.sourceInfo": // KEY_SOURCE_INFO + case "android:activity.usageTimeReport": // KEY_USAGE_TIME_REPORT + case "android:activity.rotationAnimationHint": // KEY_ROTATION_ANIMATION_HINT + case "android:instantapps.installerbundle": // KEY_INSTANT_APP_VERIFICATION_BUNDLE + case "android:activity.specsFuture": // KEY_SPECS_FUTURE + case "android:activity.remoteAnimationAdapter": // KEY_REMOTE_ANIMATION_ADAPTER + case "android:activity.remoteTransition": // KEY_REMOTE_TRANSITION + case "android:activity.overrideTaskTransition": // KEY_OVERRIDE_TASK_TRANSITION + case "android.activity.removeWithTaskOrganizer": // KEY_REMOVE_WITH_TASK_ORGANIZER + case "android.activity.launchTypeBubble": // KEY_LAUNCHED_FROM_BUBBLE + case "android.activity.splashScreenStyle": // KEY_SPLASH_SCREEN_STYLE + case "android.activity.launchIntoPipParams": // KEY_LAUNCH_INTO_PIP_PARAMS + case "android.activity.dismissKeyguard": // KEY_DISMISS_KEYGUARD + case "android.activity.pendingIntentCreatorBackgroundActivityStartMode": + // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE + case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE + // Existing keys + break; + default: + unknownKeys.add(key); + break; + } + } + + // Report if any unknown key exists. + for (String key : unknownKeys) { + Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. " + + "Please review if the given bundle should be protected with permissions."); + } + assertTrue(unknownKeys.isEmpty()); + } + public static class TrampolineActivity extends Activity { static int sTaskId; diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java index 51a7e747afce..06033c7ebf75 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java @@ -20,7 +20,6 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; -import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,13 +41,26 @@ import org.junit.Test; import java.io.File; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Supplier; +/** + * Tests for the {@link LetterboxConfigurationPersister} class. + * + * Build/Install/Run: + * atest WmTests:LetterboxConfigurationPersisterTest + */ @SmallTest @Presubmit public class LetterboxConfigurationPersisterTest { private static final long TIMEOUT = 2000L; // 2 secs + private static final int DEFAULT_REACHABILITY_TEST = -1; + private static final Supplier<Integer> DEFAULT_REACHABILITY_SUPPLIER_TEST = + () -> DEFAULT_REACHABILITY_TEST; + + private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test"; + private LetterboxConfigurationPersister mLetterboxConfigurationPersister; private Context mContext; private PersisterQueue mPersisterQueue; @@ -62,7 +74,7 @@ public class LetterboxConfigurationPersisterTest { mConfigFolder = mContext.getFilesDir(); mPersisterQueue = new PersisterQueue(); mQueueState = new QueueState(); - mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext, + mLetterboxConfigurationPersister = new LetterboxConfigurationPersister( () -> mContext.getResources().getInteger( R.integer.config_letterboxDefaultPositionForHorizontalReachability), () -> mContext.getResources().getInteger( @@ -72,7 +84,8 @@ public class LetterboxConfigurationPersisterTest { () -> mContext.getResources().getInteger( R.integer.config_letterboxDefaultPositionForTabletopModeReachability ), - mConfigFolder, mPersisterQueue, mQueueState); + mConfigFolder, mPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); mQueueListener = queueEmpty -> mQueueState.onItemAdded(); mPersisterQueue.addListener(mQueueListener); mLetterboxConfigurationPersister.start(); @@ -127,8 +140,10 @@ public class LetterboxConfigurationPersisterTest { public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() { final PersisterQueue firstPersisterQueue = new PersisterQueue(); final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister( - mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(), - firstPersisterQueue, mQueueState); + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), firstPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); firstPersister.start(); firstPersister.setLetterboxPositionForHorizontalReachability(false, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); @@ -138,8 +153,10 @@ public class LetterboxConfigurationPersisterTest { stopPersisterSafe(firstPersisterQueue); final PersisterQueue secondPersisterQueue = new PersisterQueue(); final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister( - mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(), - secondPersisterQueue, mQueueState); + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), secondPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); secondPersister.start(); final int newPositionForHorizontalReachability = secondPersister.getLetterboxPositionForHorizontalReachability(false); @@ -156,37 +173,46 @@ public class LetterboxConfigurationPersisterTest { @Test public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() { - mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false, + final PersisterQueue firstPersisterQueue = new PersisterQueue(); + final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister( + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), firstPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); + firstPersister.start(); + firstPersister.setLetterboxPositionForHorizontalReachability(false, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); - mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false, + firstPersister.setLetterboxPositionForVerticalReachability(false, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); waitForCompletion(mPersisterQueue); final int newPositionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( - false); + firstPersister.getLetterboxPositionForHorizontalReachability(false); final int newPositionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false); + firstPersister.getLetterboxPositionForVerticalReachability(false); Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, newPositionForHorizontalReachability); Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, newPositionForVerticalReachability); - deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue); - waitForCompletion(mPersisterQueue); + deleteConfiguration(firstPersister, firstPersisterQueue); + waitForCompletion(firstPersisterQueue); + stopPersisterSafe(firstPersisterQueue); + + final PersisterQueue secondPersisterQueue = new PersisterQueue(); + final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister( + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, + mContext.getFilesDir(), secondPersisterQueue, mQueueState, + LETTERBOX_CONFIGURATION_TEST_FILENAME); + secondPersister.start(); final int positionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( - false); - final int defaultPositionForHorizontalReachability = - mContext.getResources().getInteger( - R.integer.config_letterboxDefaultPositionForHorizontalReachability); - Assert.assertEquals(defaultPositionForHorizontalReachability, - positionForHorizontalReachability); + secondPersister.getLetterboxPositionForHorizontalReachability(false); final int positionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false); - final int defaultPositionForVerticalReachability = - mContext.getResources().getInteger( - R.integer.config_letterboxDefaultPositionForVerticalReachability); - Assert.assertEquals(defaultPositionForVerticalReachability, - positionForVerticalReachability); + secondPersister.getLetterboxPositionForVerticalReachability(false); + Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForHorizontalReachability); + Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForVerticalReachability); + deleteConfiguration(secondPersister, secondPersisterQueue); + waitForCompletion(secondPersisterQueue); + stopPersisterSafe(secondPersisterQueue); } private void stopPersisterSafe(PersisterQueue persisterQueue) { @@ -222,7 +248,7 @@ public class LetterboxConfigurationPersisterTest { private void deleteConfiguration(LetterboxConfigurationPersister persister, PersisterQueue persisterQueue) { final AtomicFile fileToDelete = new AtomicFile( - new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME)); + new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME)); persisterQueue.addItem( new DeleteFileCommand(fileToDelete, mQueueState.andThen( s -> persister.useDefaultValue())), true); 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/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java index 24e932f36f80..6c48a6961bc2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -16,17 +16,36 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import android.app.ActivityOptions; +import android.content.pm.ActivityInfo; +import android.os.Looper; import android.platform.test.annotations.Presubmit; +import android.view.RemoteAnimationAdapter; +import android.window.RemoteTransition; import android.window.WindowContainerToken; import androidx.test.filters.MediumTest; import org.junit.Test; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; /** * Build/Install/Run: @@ -73,4 +92,119 @@ public class SafeActivityOptionsTest { assertSame(clone.getOriginalOptions().getLaunchRootTask(), token); } + + @Test + public void test_getOptions() { + // Mock everything necessary + MockitoSession mockingSession = mockitoSession() + .mockStatic(ActivityTaskManagerService.class) + .strictness(Strictness.LENIENT) + .startMocking(); + doReturn(PERMISSION_DENIED).when(() -> ActivityTaskManagerService.checkPermission( + any(), anyInt(), anyInt())); + + final LockTaskController lockTaskController = mock(LockTaskController.class); + doReturn(false).when(lockTaskController).isPackageAllowlisted(anyInt(), any()); + + final ActivityTaskManagerService atm = mock(ActivityTaskManagerService.class); + doReturn(lockTaskController).when(atm).getLockTaskController(); + + final ActivityTaskSupervisor taskSupervisor = + new ActivityTaskSupervisor(atm, mock(Looper.class)); + spyOn(taskSupervisor); + doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnDisplay(anyInt(), + anyInt(), anyInt(), any()); + doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnTaskDisplayArea(anyInt(), + anyInt(), any(), any()); + + taskSupervisor.mRecentTasks = mock(RecentTasks.class); + doReturn(false).when(taskSupervisor.mRecentTasks).isCallerRecents(anyInt()); + + // Ensure exceptions are thrown when lack of permissions. + ActivityOptions activityOptions = ActivityOptions.makeBasic(); + try { + activityOptions.setLaunchTaskId(100); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setDisableStartingWindow(true); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setTransientLaunch(); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setDismissKeyguard(); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setLaunchActivityType(ACTIVITY_TYPE_STANDARD); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setLaunchedFromBubble(true); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setLaunchDisplayId(DEFAULT_DISPLAY); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setLockTaskEnabled(true); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeCustomTaskAnimation( + getInstrumentation().getContext(), 0, 0, null, null, null); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + RemoteAnimationAdapter remoteAnimationAdapter = mock(RemoteAnimationAdapter.class); + RemoteTransition remoteTransition = mock(RemoteTransition.class); + activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter, + remoteTransition); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setRemoteAnimationAdapter(remoteAnimationAdapter); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeRemoteTransition(remoteTransition); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + activityOptions = ActivityOptions.makeBasic(); + activityOptions.setRemoteTransition(remoteTransition); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + + verifySecureExceptionThrown(activityOptions, taskSupervisor, + mock(TaskDisplayArea.class)); + } finally { + mockingSession.finishMocking(); + } + } + + private void verifySecureExceptionThrown(ActivityOptions activityOptions, + ActivityTaskSupervisor taskSupervisor) { + verifySecureExceptionThrown(activityOptions, taskSupervisor, null /* mockTda */); + } + + private void verifySecureExceptionThrown(ActivityOptions activityOptions, + ActivityTaskSupervisor taskSupervisor, TaskDisplayArea mockTda) { + SafeActivityOptions safeActivityOptions = new SafeActivityOptions(activityOptions); + if (mockTda != null) { + spyOn(safeActivityOptions); + doReturn(mockTda).when(safeActivityOptions).getLaunchTaskDisplayArea(any(), any()); + } + + boolean isExceptionThrow = false; + final ActivityInfo aInfo = mock(ActivityInfo.class); + try { + safeActivityOptions.getOptions(null, aInfo, null, taskSupervisor); + } catch (SecurityException ex) { + isExceptionThrow = true; + } + assertTrue(isExceptionThrow); + } } 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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index efcbd31fb6e4..f1ee76e1ed94 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5268,7 +5268,11 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName()); + String[] mergedImsisFromGroup = + telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName()); + if (mergedImsisFromGroup != null) { + return mergedImsisFromGroup; + } } } catch (RemoteException ex) { } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt index 144a73121746..6722cc8fd021 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt @@ -62,14 +62,6 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding } } - /** Assert the background animation layer is never visible during bounds change transition. */ - @Presubmit - @Test - fun backgroundLayerNeverVisible() { - val backgroundColorLayer = ComponentNameMatcher("", "Animation Background") - flicker.assertLayers { isInvisible(backgroundColorLayer) } - } - /** Trampoline activity should finish itself before the end of this test. */ @Presubmit @Test 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/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt index c1db4231aa87..ea7c7c4276dc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt @@ -25,6 +25,7 @@ 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.FlakyTest import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper import com.android.server.wm.flicker.helpers.setRotation @@ -78,6 +79,7 @@ class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: LegacyF flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp) } + @FlakyTest(bugId = 290767483) @Postsubmit @Test fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() { 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 68ae806f3c8b..ff9799a1c710 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -102,6 +102,24 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".LaunchTransparentActivity" + android:resizeableActivity="false" + android:screenOrientation="portrait" + android:theme="@android:style/Theme" + android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" + android:label="LaunchTransparentActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".TransparentActivity" + android:theme="@style/TransparentTheme" + android:taskAffinity="com.android.server.wm.flicker.testapp.TransparentActivity" + android:label="TransparentActivity" + android:exported="false"> + </activity> <activity android:name=".LaunchNewActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity" android:theme="@style/CutoutShortEdges" @@ -206,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/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml new file mode 100644 index 000000000000..0730ded66ce4 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml @@ -0,0 +1,21 @@ +<?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. + --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + +</FrameLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml new file mode 100644 index 000000000000..ff4ead95f16e --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml @@ -0,0 +1,37 @@ +<?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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/black"> + + <Button + android:id="@+id/button_launch_transparent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Launch Transparent" /> + <Button + android:id="@+id/button_request_permission" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Request Permission" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 1d21fd56a487..e51ed29adebf 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -43,6 +43,13 @@ <item name="android:windowSoftInputMode">stateUnchanged</item> </style> + <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:backgroundDimEnabled">false</item> + </style> + <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowDisablePreview">true</item> </style> 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/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 95c86acb9ee9..2795a6c43015 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -73,6 +73,18 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity"); } + public static class TransparentActivity { + public static final String LABEL = "TransparentActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".TransparentActivity"); + } + + public static class LaunchTransparentActivity { + public static final String LABEL = "LaunchTransparentActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchTransparentActivity"); + } + public static class DialogThemedActivity { public static final String LABEL = "DialogThemedActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java new file mode 100644 index 000000000000..7c161fd8e611 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java @@ -0,0 +1,36 @@ +/* + * 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.testapp; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +public class LaunchTransparentActivity extends Activity { + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_transparent_launch); + findViewById(R.id.button_launch_transparent) + .setOnClickListener(v -> launchTransparentActivity()); + } + + private void launchTransparentActivity() { + startActivity(new Intent(this, TransparentActivity.class)); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java new file mode 100644 index 000000000000..1bac8bdb003a --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java @@ -0,0 +1,29 @@ +/* + * 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.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class TransparentActivity extends Activity { + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_transparent); + } +} 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 diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt index e03d92ab44a0..f1727b78f135 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt @@ -30,6 +30,7 @@ const val BINDER_CLASS = "android.os.Binder" const val IINTERFACE_INTERFACE = "android.os.IInterface" const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission" +const val PERMISSION_PREFIX_LITERAL = "android.permission." /** * If a non java (e.g. c++) backend is enabled, the @EnforcePermission diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt index 0a66cd510353..3a95df9b2773 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt @@ -95,7 +95,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { val v1 = ConstantEvaluator.evaluate(context, value1) val v2 = ConstantEvaluator.evaluate(context, value2) if (v1 != null && v2 != null) { - if (v1 != v2) { + if (v1 != v2 && !isOneShortPermissionOfOther(v1, v2)) { return false } } else { @@ -107,7 +107,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { for (j in children1.indices) { val c1 = ConstantEvaluator.evaluate(context, children1[j]) val c2 = ConstantEvaluator.evaluate(context, children2[j]) - if (c1 != c2) { + if (c1 != c2 && !isOneShortPermissionOfOther(c1, c2)) { return false } } @@ -116,6 +116,12 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { return true } + private fun isOneShortPermissionOfOther( + permission1: Any?, + permission2: Any? + ): Boolean = permission1 == (permission2 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL) || + permission2 == (permission1 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL) + private fun compareMethods( context: JavaContext, element: UElement, diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt index 75b00737a168..b3dacbdf57a6 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt @@ -316,20 +316,55 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation] public void testMethod() {} ~~~~~~~~~~ - 1 errors, 0 warnings + 1 errors, 0 warnings """.addLineContinuation() ) } - fun testDoesDetectIssuesShortStringsNotAllowed() { + fun testDoesNotDetectIssuesShortStringsAllowedInChildAndParent() { lint().files(java( """ package test.pkg; import android.annotation.EnforcePermission; public class TestClass121 extends IFooMethod.Stub { @Override + @EnforcePermission("READ_PHONE_STATE") + public void testMethod() {} + @Override + @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethodParentShortPermission() {} + @Override @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) public void testMethodAnyLiteral() {} + @Override + @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAnyLiteralParentsShortPermission() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDoesDetectIssuesWrongShortStringsInChildAndParent() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass121 extends IFooMethod.Stub { + @Override + @EnforcePermission("READ_WRONG_PHONE_STATE") + public void testMethod() {} + @Override + @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE) + public void testMethodParentShortPermission() {} + @Override + @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"}) + public void testMethodAnyLiteral() {} + @Override + @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE}) + public void testMethodAnyLiteralParentsShortPermission() {} } """).indented(), *stubs @@ -337,14 +372,19 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { .run() .expect( """ - src/test/pkg/TestClass121.java:6: Error: The method \ - TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \ - which differs from the overridden method Stub.testMethodAnyLiteral: \ - @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \ - The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] - public void testMethodAnyLiteral() {} - ~~~~~~~~~~~~~~~~~~~~ - 1 errors, 0 warnings + src/test/pkg/TestClass121.java:6: Error: The method TestClass121.testMethod is annotated with @EnforcePermission("READ_WRONG_PHONE_STATE") which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ + src/test/pkg/TestClass121.java:9: Error: The method TestClass121.testMethodParentShortPermission is annotated with @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE) which differs from the overridden method Stub.testMethodParentShortPermission: @android.annotation.EnforcePermission("READ_PHONE_STATE"). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodParentShortPermission() {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass121.java:12: Error: The method TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"}) which differs from the overridden method Stub.testMethodAnyLiteral: @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAnyLiteral() {} + ~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass121.java:15: Error: The method TestClass121.testMethodAnyLiteralParentsShortPermission is annotated with @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE}) which differs from the overridden method Stub.testMethodAnyLiteralParentsShortPermission: @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAnyLiteralParentsShortPermission() {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 4 errors, 0 warnings """.addLineContinuation() ) } @@ -360,12 +400,18 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) public void testMethod() {} @Override + @android.annotation.EnforcePermission("READ_PHONE_STATE") + public void testMethodParentShortPermission() {} + @Override @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) public void testMethodAny() {} @Override @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}) public void testMethodAnyLiteral() {} @Override + @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) + public void testMethodAnyLiteralParentsShortPermission() {} + @Override @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) public void testMethodAll() {} @Override @@ -374,10 +420,14 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { } @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) public void testMethod(); + @android.annotation.EnforcePermission("READ_PHONE_STATE") + public void testMethodParentShortPermission(); @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) public void testMethodAny() {} @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}) public void testMethodAnyLiteral() {} + @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) + public void testMethodAnyLiteralParentsShortPermission() {} @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) public void testMethodAll() {} @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}) @@ -404,6 +454,7 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { package android.Manifest; class permission { public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; + public static final String READ_WRONG_PHONE_STATE = "android.permission.READ_WRONG_PHONE_STATE"; public static final String NFC = "android.permission.NFC"; public static final String INTERNET = "android.permission.INTERNET"; } |