diff options
323 files changed, 8925 insertions, 2411 deletions
diff --git a/Android.bp b/Android.bp index 57b239e73ce9..72e519cc04ff 100644 --- a/Android.bp +++ b/Android.bp @@ -100,6 +100,10 @@ filegroup { ":android.hardware.gnss-V2-java-source", ":android.hardware.graphics.common-V3-java-source", ":android.hardware.keymaster-V4-java-source", + ":android.hardware.radio-V3-java-source", + ":android.hardware.radio.data-V3-java-source", + ":android.hardware.radio.network-V3-java-source", + ":android.hardware.radio.voice-V3-java-source", ":android.hardware.security.keymint-V3-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.thermal-V1-java-source", @@ -212,12 +216,6 @@ java_library { "android.hardware.gnss-V1.0-java", "android.hardware.gnss-V2.1-java", "android.hardware.health-V1.0-java-constants", - "android.hardware.radio-V1.0-java", - "android.hardware.radio-V1.1-java", - "android.hardware.radio-V1.2-java", - "android.hardware.radio-V1.3-java", - "android.hardware.radio-V1.4-java", - "android.hardware.radio-V1.5-java", "android.hardware.radio-V1.6-java", "android.hardware.radio.data-V3-java", "android.hardware.radio.ims-V2-java", @@ -650,8 +648,6 @@ stubs_defaults { libs: [ "android.hardware.cas-V1.2-java", "android.hardware.health-V1.0-java-constants", - "android.hardware.radio-V1.5-java", - "android.hardware.radio-V1.6-java", "android.hardware.thermal-V1.0-java-constants", "android.hardware.thermal-V2.0-java", "android.hardware.tv.input-V1.0-java-constants", diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java index a69d3ffa46fa..818e11b407d5 100644 --- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java +++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java @@ -18,6 +18,7 @@ package android.view; import static org.junit.Assert.assertTrue; +import android.app.UiAutomation; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; @@ -29,8 +30,8 @@ import android.widget.LinearLayout; import androidx.benchmark.BenchmarkState; import androidx.benchmark.junit4.BenchmarkRule; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import org.junit.Rule; @@ -142,6 +143,10 @@ public class ViewShowHidePerfTest { } private void testParentWithChild(TestCallback callback) throws Throwable { + // Make sure that a11y is disabled to prevent the test affected by accessibility events. + InstrumentationRegistry.getInstrumentation() + .getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY); + mActivityRule.runOnUiThread(() -> { final BenchmarkState state = mBenchmarkRule.getState(); diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java index a9f720ac2ba0..515ddc8d1d49 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java @@ -80,7 +80,7 @@ public class BenchmarkRunner { private void prepareForNextRun() { SystemClock.sleep(COOL_OFF_PERIOD_MS); - ShellHelper.runShellCommand("am wait-for-broadcast-idle"); + ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers"); mStartTimeNs = System.nanoTime(); mPausedDurationNs = 0; } @@ -102,7 +102,7 @@ public class BenchmarkRunner { * to avoid unnecessary waiting. */ public void resumeTiming() { - ShellHelper.runShellCommand("am wait-for-broadcast-idle"); + ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers"); resumeTimer(); } diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 354c741a4bd2..da700aaca047 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -1541,7 +1541,8 @@ public class UserLifecycleTests { private void waitForBroadcastIdle() { try { - ShellHelper.runShellCommandWithTimeout("am wait-for-broadcast-idle", TIMEOUT_IN_SECOND); + ShellHelper.runShellCommandWithTimeout( + "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND); } catch (TimeoutException e) { Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e); } diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 4e8bc7cfabf4..93d20dd199bf 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -213,7 +213,6 @@ java_defaults { system_modules: "none", java_version: "1.8", compile_dex: true, - defaults_visibility: ["//visibility:private"], visibility: ["//visibility:public"], } @@ -230,8 +229,6 @@ java_defaults { tag: ".jar", dest: "android-non-updatable.jar", }, - defaults_visibility: ["//visibility:private"], - visibility: ["//visibility:private"], } java_library { @@ -453,7 +450,6 @@ java_library { java_genrule { name: "android_stubs_private_hjar", - visibility: ["//visibility:private"], srcs: [":android_stubs_private_jar{.hjar}"], out: ["android_stubs_private.jar"], cmd: "cp $(in) $(out)", @@ -462,7 +458,6 @@ java_genrule { java_library { name: "android_stubs_private", defaults: ["android_stubs_dists_default"], - visibility: ["//visibility:private"], sdk_version: "none", system_modules: "none", static_libs: ["android_stubs_private_hjar"], @@ -473,7 +468,6 @@ java_library { java_genrule { name: "android_stubs_private_framework_aidl", - visibility: ["//visibility:private"], tools: ["sdkparcelables"], srcs: [":android_stubs_private"], out: ["framework.aidl"], @@ -637,7 +631,6 @@ droidstubs { "metalava-manual", ], args: priv_apps, - visibility: ["//visibility:private"], } java_library { diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index de60581564b1..55ec7dae16b1 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -28,11 +28,14 @@ cc_defaults { tidy_checks: [ "modernize-*", "-modernize-avoid-c-arrays", + "-modernize-use-nodiscard", "-modernize-use-trailing-return-type", "android-*", "misc-*", + "-misc-const-correctness", "readability-*", "-readability-identifier-length", + "-readability-implicit-bool-conversion", ], tidy_checks_as_errors: [ "modernize-*", @@ -56,6 +59,7 @@ cc_defaults { "-readability-const-return-type", "-readability-convert-member-functions-to-static", "-readability-duplicate-include", + "-readability-implicit-bool-conversion", "-readability-else-after-return", "-readability-named-parameter", "-readability-redundant-access-specifiers", diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index 3b7ab9ce9560..b94b3b458065 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -59,7 +59,7 @@ using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask namespace { -constexpr const char* kFrameworkPath = "/system/framework/framework-res.apk"; +constexpr std::string_view kFrameworkPath = "/system/framework/framework-res.apk"; Status ok() { return Status::ok(); @@ -207,23 +207,47 @@ Status Idmap2Service::createIdmap(const std::string& target_path, const std::str idmap2::Result<Idmap2Service::TargetResourceContainerPtr> Idmap2Service::GetTargetContainer( const std::string& target_path) { - if (target_path == kFrameworkPath) { - if (framework_apk_cache_ == nullptr) { - // Initialize the framework APK cache. - auto target = TargetResourceContainer::FromPath(target_path); - if (!target) { - return target.GetError(); + const bool is_framework = target_path == kFrameworkPath; + bool use_cache; + struct stat st = {}; + if (is_framework || !::stat(target_path.c_str(), &st)) { + use_cache = true; + } else { + LOG(WARNING) << "failed to stat target path '" << target_path << "' for the cache"; + use_cache = false; + } + + if (use_cache) { + std::lock_guard lock(container_cache_mutex_); + if (auto cache_it = container_cache_.find(target_path); cache_it != container_cache_.end()) { + const auto& item = cache_it->second; + if (is_framework || + (item.dev == st.st_dev && item.inode == st.st_ino && item.size == st.st_size + && item.mtime.tv_sec == st.st_mtim.tv_sec && item.mtime.tv_nsec == st.st_mtim.tv_nsec)) { + return {item.apk.get()}; } - framework_apk_cache_ = std::move(*target); + container_cache_.erase(cache_it); } - return {framework_apk_cache_.get()}; } auto target = TargetResourceContainer::FromPath(target_path); if (!target) { return target.GetError(); } - return {std::move(*target)}; + if (!use_cache) { + return {std::move(*target)}; + } + + const auto res = target->get(); + std::lock_guard lock(container_cache_mutex_); + container_cache_.emplace(target_path, CachedContainer { + .dev = dev_t(st.st_dev), + .inode = ino_t(st.st_ino), + .size = st.st_size, + .mtime = st.st_mtim, + .apk = std::move(*target) + }); + return {res}; } Status Idmap2Service::createFabricatedOverlay( diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h index cc8cc5f218d2..a69fa6119974 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.h +++ b/cmds/idmap2/idmap2d/Idmap2Service.h @@ -75,7 +75,20 @@ class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 { private: // idmap2d is killed after a period of inactivity, so any information stored on this class should // be able to be recalculated if idmap2 dies and restarts. - std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_; + + // A cache item for the resource containers (apks or frros), with all information needed to + // detect if it has changed since it was parsed: + // - (dev, inode) pair uniquely identifies a file on a particular device partition (see stat(2)). + // - (mtime, size) ensure the file data hasn't changed inside that file. + struct CachedContainer { + dev_t dev; + ino_t inode; + int64_t size; + struct timespec mtime; + std::unique_ptr<idmap2::TargetResourceContainer> apk; + }; + std::unordered_map<std::string, CachedContainer> container_cache_; + std::mutex container_cache_mutex_; int32_t frro_iter_id_ = 0; std::optional<std::filesystem::directory_iterator> frro_iter_; diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index c2f010097fda..05a43ad7d936 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -536,7 +536,7 @@ status_t IncidentService::onTransact(uint32_t code, const Parcel& data, Parcel* fflush(fout); fclose(fout); } - if (fout != NULL) { + if (ferr != NULL) { fflush(ferr); fclose(ferr); } diff --git a/core/api/current.txt b/core/api/current.txt index 83a92e28d252..1d3e275ceb3a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12181,32 +12181,32 @@ package android.content.pm { field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2 field public static final int REQUESTED_PERMISSION_IMPLICIT = 4; // 0x4 field public static final int REQUESTED_PERMISSION_NEVER_FOR_LOCATION = 65536; // 0x10000 - field public android.content.pm.ActivityInfo[] activities; + field @Nullable public android.content.pm.ActivityInfo[] activities; field @Nullable public android.content.pm.ApplicationInfo applicationInfo; field @Nullable public android.content.pm.Attribution[] attributions; field public int baseRevisionCode; - field public android.content.pm.ConfigurationInfo[] configPreferences; - field public android.content.pm.FeatureGroupInfo[] featureGroups; + field @Nullable public android.content.pm.ConfigurationInfo[] configPreferences; + field @Nullable public android.content.pm.FeatureGroupInfo[] featureGroups; field public long firstInstallTime; - field public int[] gids; + field @Nullable public int[] gids; field public int installLocation; - field public android.content.pm.InstrumentationInfo[] instrumentation; + field @Nullable public android.content.pm.InstrumentationInfo[] instrumentation; field public boolean isApex; field public long lastUpdateTime; - field public String packageName; - field public android.content.pm.PermissionInfo[] permissions; - field public android.content.pm.ProviderInfo[] providers; - field public android.content.pm.ActivityInfo[] receivers; - field public android.content.pm.FeatureInfo[] reqFeatures; - field public String[] requestedPermissions; - field public int[] requestedPermissionsFlags; - field public android.content.pm.ServiceInfo[] services; - field public String sharedUserId; + field @NonNull public String packageName; + field @Nullable public android.content.pm.PermissionInfo[] permissions; + field @Nullable public android.content.pm.ProviderInfo[] providers; + field @Nullable public android.content.pm.ActivityInfo[] receivers; + field @Nullable public android.content.pm.FeatureInfo[] reqFeatures; + field @Nullable public String[] requestedPermissions; + field @Nullable public int[] requestedPermissionsFlags; + field @Nullable public android.content.pm.ServiceInfo[] services; + field @Nullable public String sharedUserId; field public int sharedUserLabel; - field @Deprecated public android.content.pm.Signature[] signatures; - field public android.content.pm.SigningInfo signingInfo; - field public String[] splitNames; - field public int[] splitRevisionCodes; + field @Deprecated @Nullable public android.content.pm.Signature[] signatures; + field @Nullable public android.content.pm.SigningInfo signingInfo; + field @NonNull public String[] splitNames; + field @NonNull public int[] splitRevisionCodes; field @Deprecated public int versionCode; field @Nullable public String versionName; } @@ -13003,7 +13003,7 @@ package android.content.pm { method public final int getIconResource(); method public boolean isCrossProfileIntentForwarderActivity(); method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); - method public CharSequence loadLabel(android.content.pm.PackageManager); + method @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ResolveInfo> CREATOR; field public android.content.pm.ActivityInfo activityInfo; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 99814770dd77..b60b63c9b1d8 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5006,12 +5006,6 @@ public class Notification implements Parcelable return mUserExtras; } - private Bundle getAllExtras() { - final Bundle saveExtras = (Bundle) mUserExtras.clone(); - saveExtras.putAll(mN.extras); - return saveExtras; - } - /** * Add an action to this notification. Actions are typically displayed by * the system as a button adjacent to the notification content. @@ -6617,9 +6611,16 @@ public class Notification implements Parcelable + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); } - // first, add any extras from the calling code + // Adds any new extras provided by the user. if (mUserExtras != null) { - mN.extras = getAllExtras(); + final Bundle saveExtras = (Bundle) mUserExtras.clone(); + if (SystemProperties.getBoolean( + "persist.sysui.notification.builder_extras_override", false)) { + mN.extras.putAll(saveExtras); + } else { + saveExtras.putAll(mN.extras); + mN.extras = saveExtras; + } } mN.creationTime = System.currentTimeMillis(); diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 776e34bb4792..385fd509757b 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -24,9 +24,11 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; +import android.compat.annotation.LoggingOnly; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -49,6 +51,7 @@ import android.util.Slog; import android.view.KeyEvent; import android.view.View; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.statusbar.AppClipsServiceConnector; import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBarService; @@ -170,6 +173,8 @@ public class StatusBarManager { public @interface Disable2Flags {} // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt) + private static final String TAG = "StatusBarManager"; + /** * Default disable flags for setup * @@ -572,13 +577,13 @@ public class StatusBarManager { private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L; /** - * Media controls based on {@link android.app.Notification.MediaStyle} notifications will be - * required to include a non-empty title, either in the {@link android.media.MediaMetadata} or + * Media controls based on {@link android.app.Notification.MediaStyle} notifications should + * include a non-empty title, either in the {@link android.media.MediaMetadata} or * notification title. */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L; + @LoggingOnly + private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L; @UnsupportedAppUsage private Context mContext; @@ -586,6 +591,9 @@ public class StatusBarManager { @UnsupportedAppUsage private IBinder mToken = new Binder(); + private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + @UnsupportedAppUsage StatusBarManager(Context context) { mContext = context; @@ -597,7 +605,7 @@ public class StatusBarManager { mService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); if (mService == null) { - Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE"); + Slog.w(TAG, "warning: no STATUS_BAR_SERVICE"); } } return mService; @@ -1226,18 +1234,22 @@ public class StatusBarManager { } /** - * Checks whether the given package must include a non-empty title for its media controls. + * Log that the given package has posted media controls with a blank title * * @param packageName App posting media controls - * @param user Current user handle - * @return true if the app is required to provide a non-empty title + * @param userId Current user ID + * @throws RuntimeException if there is an error reporting the change * * @hide */ - @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, - android.Manifest.permission.LOG_COMPAT_CHANGE}) - public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) { - return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user); + public void logBlankMediaTitle(String packageName, @UserIdInt int userId) + throws RuntimeException { + try { + mPlatformCompat.reportChangeByPackageName(MEDIA_CONTROL_BLANK_TITLE, packageName, + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 8977a30fbee8..63c11b779641 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -31,11 +32,13 @@ public class PackageInfo implements Parcelable { * The name of this package. From the <manifest> tag's "name" * attribute. */ + @NonNull public String packageName; /** * The names of any installed split APKs for this package. */ + @NonNull public String[] splitNames; /** @@ -110,6 +113,7 @@ public class PackageInfo implements Parcelable { * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} * attribute. Indexes are a 1:1 mapping against {@link #splitNames}. */ + @NonNull public int[] splitRevisionCodes; /** @@ -117,6 +121,7 @@ public class PackageInfo implements Parcelable { * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId} * attribute. */ + @Nullable public String sharedUserId; /** @@ -149,6 +154,7 @@ public class PackageInfo implements Parcelable { * All kernel group-IDs that have been assigned to this package. * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set. */ + @Nullable public int[] gids; /** @@ -157,6 +163,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_ACTIVITIES} was set. */ + @Nullable public ActivityInfo[] activities; /** @@ -165,6 +172,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_RECEIVERS} was set. */ + @Nullable public ActivityInfo[] receivers; /** @@ -173,6 +181,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_SERVICES} was set. */ + @Nullable public ServiceInfo[] services; /** @@ -181,6 +190,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_PROVIDERS} was set. */ + @Nullable public ProviderInfo[] providers; /** @@ -189,6 +199,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_INSTRUMENTATION} was set. */ + @Nullable public InstrumentationInfo[] instrumentation; /** @@ -197,6 +208,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_PERMISSIONS} was set. */ + @Nullable public PermissionInfo[] permissions; /** @@ -207,6 +219,7 @@ public class PackageInfo implements Parcelable { * all permissions requested, even those that were not granted or known * by the system at install time. */ + @Nullable public String[] requestedPermissions; /** @@ -218,6 +231,7 @@ public class PackageInfo implements Parcelable { * the flags {@link #REQUESTED_PERMISSION_GRANTED} and * {@link #REQUESTED_PERMISSION_NEVER_FOR_LOCATION} set as appropriate. */ + @Nullable public int[] requestedPermissionsFlags; /** @@ -226,7 +240,8 @@ public class PackageInfo implements Parcelable { * is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS_LONG} was set. */ @SuppressWarnings({"ArrayReturn", "NullableCollection"}) - public @Nullable Attribution[] attributions; + @Nullable + public Attribution[] attributions; /** * Flag for {@link #requestedPermissionsFlags}: the requested permission @@ -283,6 +298,7 @@ public class PackageInfo implements Parcelable { * @deprecated use {@code signingInfo} instead */ @Deprecated + @Nullable public Signature[] signatures; /** @@ -294,6 +310,7 @@ public class PackageInfo implements Parcelable { * Use this field instead of the deprecated {@code signatures} field. * See {@link SigningInfo} for more information on its contents. */ + @Nullable public SigningInfo signingInfo; /** @@ -303,6 +320,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_CONFIGURATIONS} was set. */ + @Nullable public ConfigurationInfo[] configPreferences; /** @@ -310,6 +328,7 @@ public class PackageInfo implements Parcelable { * * @see FeatureInfo#FLAG_REQUIRED */ + @Nullable public FeatureInfo[] reqFeatures; /** @@ -320,6 +339,7 @@ public class PackageInfo implements Parcelable { * * @see FeatureInfo#FLAG_REQUIRED */ + @Nullable public FeatureGroupInfo[] featureGroups; /** @@ -388,12 +408,14 @@ public class PackageInfo implements Parcelable { * The restricted account authenticator type that is used by this application. * @hide */ + @Nullable public String restrictedAccountType; /** * The required account type without which this application will not function. * @hide */ + @Nullable public String requiredAccountType; /** @@ -403,6 +425,7 @@ public class PackageInfo implements Parcelable { * @hide */ @UnsupportedAppUsage + @Nullable public String overlayTarget; /** @@ -411,6 +434,7 @@ public class PackageInfo implements Parcelable { * Overlayable name defined within the target package, or null. * @hide */ + @Nullable public String targetOverlayableName; /** @@ -418,6 +442,7 @@ public class PackageInfo implements Parcelable { * * @hide */ + @Nullable public String overlayCategory; /** @hide */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 1973c627d2bf..dca4544f9a14 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2883,6 +2883,20 @@ public abstract class PackageManager { "android.software.car.templates_host"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:If this + * feature is supported, the device should also declare {@link #FEATURE_AUTOMOTIVE} and show + * a UI that can display multiple tasks at the same time on a single display. The user can + * perform multiple actions on different tasks simultaneously. Apps open in split screen mode + * by default, instead of full screen. Unlike Android's multi-window mode, where users can + * choose how to display apps, the device determines how apps are shown. + * + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAR_SPLITSCREEN_MULTITASKING = + "android.software.car.splitscreen_multitasking"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index f90044027b09..02a4980d014f 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -33,6 +34,7 @@ import android.util.Slog; import java.text.Collator; import java.util.Comparator; +import java.util.Objects; /** * Information that is returned from resolving an intent @@ -224,10 +226,17 @@ public class ResolveInfo implements Parcelable { * @return Returns a CharSequence containing the resolutions's label. If the * item does not have a label, its name is returned. */ - public CharSequence loadLabel(PackageManager pm) { + @NonNull + public CharSequence loadLabel(@NonNull PackageManager pm) { if (nonLocalizedLabel != null) { return nonLocalizedLabel; } + + // In order to not change the original behavior. To add null check here to support backward + // compatible. If nonLocalizedLabel is not null, we also return nonLocalizedLabel even if pm + // is null. + Objects.requireNonNull(pm); + CharSequence label; if (resolvePackageName != null && labelRes != 0) { label = pm.getText(resolvePackageName, labelRes, null); diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 6b044fc5bfb6..82694ee3463b 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -97,27 +97,6 @@ public class BiometricManager { public @interface BiometricError {} /** - * Single sensor or unspecified multi-sensor behavior (prefer an explicit choice if the - * device is multi-sensor). - * @hide - */ - public static final int BIOMETRIC_MULTI_SENSOR_DEFAULT = 0; - - /** - * Use face and fingerprint sensors together. - * @hide - */ - public static final int BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE = 1; - - /** - * @hide - */ - @IntDef({BIOMETRIC_MULTI_SENSOR_DEFAULT, - BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE}) - @Retention(RetentionPolicy.SOURCE) - public @interface BiometricMultiSensorMode {} - - /** * Types of authenticators, defined at a level of granularity supported by * {@link BiometricManager} and {@link BiometricPrompt}. * diff --git a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl index 450c5ceab04c..45f1c8ae26c0 100644 --- a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl @@ -29,5 +29,7 @@ oneway interface IBiometricSysuiReceiver { // Notifies the client that an internal event, e.g. back button has occurred. void onSystemEvent(int event); // Notifies that the dialog has finished animating. - void onDialogAnimatedIn(); + void onDialogAnimatedIn(boolean startFingerprintNow); + // Notifies that the fingerprint should start now (after onDialogAnimatedIn(false)). + void onStartFingerprintNow(); } diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 69105016e0ea..78388efe98c7 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -246,8 +246,7 @@ final class NavigationBarController { @Override public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets, @NonNull ViewTreeObserver.InternalInsetsInfo dest) { - if (!mImeDrawsImeNavBar || mNavigationBarFrame == null - || mService.isExtractViewShown()) { + if (!mImeDrawsImeNavBar || mNavigationBarFrame == null) { return; } @@ -255,53 +254,58 @@ final class NavigationBarController { if (systemInsets != null) { final Window window = mService.mWindow.getWindow(); final View decor = window.getDecorView(); - Region touchableRegion = null; - final View inputFrame = mService.mInputFrame; - switch (originalInsets.touchableInsets) { - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: - if (inputFrame.getVisibility() == View.VISIBLE) { - inputFrame.getLocationInWindow(mTempPos); - mTempRect.set(mTempPos[0], mTempPos[1], - mTempPos[0] + inputFrame.getWidth(), - mTempPos[1] + inputFrame.getHeight()); - touchableRegion = new Region(mTempRect); - } - break; - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: - if (inputFrame.getVisibility() == View.VISIBLE) { - inputFrame.getLocationInWindow(mTempPos); - mTempRect.set(mTempPos[0], originalInsets.contentTopInsets, - mTempPos[0] + inputFrame.getWidth() , - mTempPos[1] + inputFrame.getHeight()); - touchableRegion = new Region(mTempRect); - } - break; - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: - if (inputFrame.getVisibility() == View.VISIBLE) { - inputFrame.getLocationInWindow(mTempPos); - mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets, - mTempPos[0] + inputFrame.getWidth(), - mTempPos[1] + inputFrame.getHeight()); - touchableRegion = new Region(mTempRect); - } - break; - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: - touchableRegion = new Region(); - touchableRegion.set(originalInsets.touchableRegion); - break; - } - // Hereafter "mTempRect" means a navigation bar rect. - mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom, - decor.getRight(), decor.getBottom()); - if (touchableRegion == null) { - touchableRegion = new Region(mTempRect); - } else { - touchableRegion.union(mTempRect); - } - dest.touchableRegion.set(touchableRegion); - dest.setTouchableInsets( - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + // If the extract view is shown, everything is touchable, so no need to update + // touchable insets, but we still update normal insets below. + if (!mService.isExtractViewShown()) { + Region touchableRegion = null; + final View inputFrame = mService.mInputFrame; + switch (originalInsets.touchableInsets) { + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + if (inputFrame.getVisibility() == View.VISIBLE) { + inputFrame.getLocationInWindow(mTempPos); + mTempRect.set(mTempPos[0], mTempPos[1], + mTempPos[0] + inputFrame.getWidth(), + mTempPos[1] + inputFrame.getHeight()); + touchableRegion = new Region(mTempRect); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: + if (inputFrame.getVisibility() == View.VISIBLE) { + inputFrame.getLocationInWindow(mTempPos); + mTempRect.set(mTempPos[0], originalInsets.contentTopInsets, + mTempPos[0] + inputFrame.getWidth(), + mTempPos[1] + inputFrame.getHeight()); + touchableRegion = new Region(mTempRect); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: + if (inputFrame.getVisibility() == View.VISIBLE) { + inputFrame.getLocationInWindow(mTempPos); + mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets, + mTempPos[0] + inputFrame.getWidth(), + mTempPos[1] + inputFrame.getHeight()); + touchableRegion = new Region(mTempRect); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: + touchableRegion = new Region(); + touchableRegion.set(originalInsets.touchableRegion); + break; + } + // Hereafter "mTempRect" means a navigation bar rect. + mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom, + decor.getRight(), decor.getBottom()); + if (touchableRegion == null) { + touchableRegion = new Region(mTempRect); + } else { + touchableRegion.union(mTempRect); + } + + dest.touchableRegion.set(touchableRegion); + dest.setTouchableInsets( + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + } // TODO(b/215443343): See if we can use View#OnLayoutChangeListener(). // TODO(b/215443343): See if we can replace DecorView#mNavigationColorViewState.view diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 6f2a915cee46..3f4013908612 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -37,7 +37,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.preference.VolumePreference.VolumeStore; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.System; @@ -47,7 +46,6 @@ import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import com.android.internal.annotations.GuardedBy; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.os.SomeArgs; import java.util.concurrent.TimeUnit; @@ -295,14 +293,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (zenMuted) { mSeekBar.setProgress(mLastAudibleStreamVolume, true); } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { - /** - * the first variable above is preserved and the conditions below are made explicit - * so that when user attempts to slide the notification seekbar out of vibrate the - * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased - */ - if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) - || mStreamType == AudioManager.STREAM_RING + // For ringer-mode affected streams, show volume as zero when ringermode is vibrate + if (mStreamType == AudioManager.STREAM_RING || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) { mSeekBar.setProgress(0, true); } @@ -397,9 +389,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba // set the time of stop volume if ((mStreamType == AudioManager.STREAM_VOICE_CALL || mStreamType == AudioManager.STREAM_RING - || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) - && mStreamType == AudioManager.STREAM_NOTIFICATION) + || mStreamType == AudioManager.STREAM_NOTIFICATION || mStreamType == AudioManager.STREAM_ALARM)) { sStopVolumeTime = java.lang.System.currentTimeMillis(); } @@ -686,10 +676,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } private void updateVolumeSlider(int streamType, int streamValue) { - final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) - && mNotificationOrRing ? isNotificationOrRing(streamType) : - streamType == mStreamType; + final boolean streamMatch = (streamType == mStreamType); if (mSeekBar != null && streamMatch && streamValue != -1) { final boolean muted = mAudioManager.isStreamMute(mStreamType) || streamValue == 0; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index dce4902d2970..c444156426f4 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -15376,18 +15376,6 @@ public final class Settings { public static final String ANGLE_EGL_FEATURES = "angle_egl_features"; /** - * Comma-separated list of package names that ANGLE may have issues with - * @hide - */ - public static final String ANGLE_DEFERLIST = "angle_deferlist"; - - /** - * Integer mode of the logic for applying `angle_deferlist` - * @hide - */ - public static final String ANGLE_DEFERLIST_MODE = "angle_deferlist_mode"; - - /** * Show the "ANGLE In Use" dialog box to the user when ANGLE is the OpenGL driver. * The value is a boolean (1 or 0). * @hide diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 25ee6aff6ac4..795500aef103 100755 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -275,8 +275,12 @@ public class DisplayMetrics { */ public float density; /** - * The screen density expressed as dots-per-inch. May be either - * {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}. + * The screen density expressed as dots-per-inch. May be any one of the + * {@code DENSITY_} constants defined above. + * + * New constants are frequently added, and constants added on new Android + * versions may be backported to previous Android versions, so applications + * should not strongly rely on density matching one of the enum constants. */ public int densityDpi; /** diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java index 470c2801d838..a47f34f4125e 100644 --- a/core/java/android/view/InsetsFrameProvider.java +++ b/core/java/android/view/InsetsFrameProvider.java @@ -164,6 +164,10 @@ public class InsetsFrameProvider implements Parcelable { return mFlags; } + public boolean hasFlags(@Flags int mask) { + return (mFlags & mask) == mask; + } + public InsetsFrameProvider setInsetsSize(Insets insetsSize) { mInsetsSize = insetsSize; return this; diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 9fc42fff7084..e10184976abe 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -58,9 +58,20 @@ public class InsetsSource implements Parcelable { */ public static final int FLAG_SUPPRESS_SCRIM = 1; + /** + * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the + * insets frame size when calculating the rounded corner insets to other windows. + * + * For example, task bar will draw fake rounded corners above itself, so we need to move the + * rounded corner up by the task bar insets size to make other windows see a rounded corner + * above the task bar. + */ + public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1; + @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = "FLAG_", value = { FLAG_SUPPRESS_SCRIM, + FLAG_INSETS_ROUNDED_CORNER, }) public @interface Flags {} @@ -78,7 +89,6 @@ public class InsetsSource implements Parcelable { private @Nullable Rect mVisibleFrame; private boolean mVisible; - private boolean mInsetsRoundedCornerFrame; private final Rect mTmpFrame = new Rect(); @@ -98,7 +108,6 @@ public class InsetsSource implements Parcelable { ? new Rect(other.mVisibleFrame) : null; mFlags = other.mFlags; - mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame; } public void set(InsetsSource other) { @@ -108,7 +117,6 @@ public class InsetsSource implements Parcelable { ? new Rect(other.mVisibleFrame) : null; mFlags = other.mFlags; - mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame; } public InsetsSource setFrame(int left, int top, int right, int bottom) { @@ -136,6 +144,11 @@ public class InsetsSource implements Parcelable { return this; } + public InsetsSource setFlags(@Flags int flags, @Flags int mask) { + mFlags = (mFlags & ~mask) | (flags & mask); + return this; + } + public int getId() { return mId; } @@ -160,20 +173,15 @@ public class InsetsSource implements Parcelable { return mFlags; } + public boolean hasFlags(int flags) { + return (mFlags & flags) == flags; + } + boolean isUserControllable() { // If mVisibleFrame is null, it will be the same area as mFrame. return mVisibleFrame == null || !mVisibleFrame.isEmpty(); } - public boolean insetsRoundedCornerFrame() { - return mInsetsRoundedCornerFrame; - } - - public InsetsSource setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) { - mInsetsRoundedCornerFrame = insetsRoundedCornerFrame; - return this; - } - /** * Calculates the insets this source will cause to a client window. * @@ -317,6 +325,9 @@ public class InsetsSource implements Parcelable { if ((flags & FLAG_SUPPRESS_SCRIM) != 0) { joiner.add("SUPPRESS_SCRIM"); } + if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) { + joiner.add("INSETS_ROUNDED_CORNER"); + } return joiner.toString(); } @@ -347,7 +358,6 @@ public class InsetsSource implements Parcelable { } pw.print(" visible="); pw.print(mVisible); pw.print(" flags="); pw.print(flagsToString(mFlags)); - pw.print(" insetsRoundedCornerFrame="); pw.print(mInsetsRoundedCornerFrame); pw.println(); } @@ -372,14 +382,12 @@ public class InsetsSource implements Parcelable { if (mFlags != that.mFlags) return false; if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true; if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; - if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false; return mFrame.equals(that.mFrame); } @Override public int hashCode() { - return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, - mInsetsRoundedCornerFrame); + return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags); } public InsetsSource(Parcel in) { @@ -393,7 +401,6 @@ public class InsetsSource implements Parcelable { } mVisible = in.readBoolean(); mFlags = in.readInt(); - mInsetsRoundedCornerFrame = in.readBoolean(); } @Override @@ -414,7 +421,6 @@ public class InsetsSource implements Parcelable { } dest.writeBoolean(mVisible); dest.writeInt(mFlags); - dest.writeBoolean(mInsetsRoundedCornerFrame); } @Override @@ -424,7 +430,6 @@ public class InsetsSource implements Parcelable { + " mFrame=" + mFrame.toShortString() + " mVisible=" + mVisible + " mFlags=[" + flagsToString(mFlags) + "]" - + (mInsetsRoundedCornerFrame ? " insetsRoundedCornerFrame" : "") + "}"; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 5b974cdb2bca..61a72772200c 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.InsetsStateProto.DISPLAY_CUTOUT; import static android.view.InsetsStateProto.DISPLAY_FRAME; import static android.view.InsetsStateProto.SOURCES; @@ -219,7 +220,7 @@ public class InsetsState implements Parcelable { final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame); for (int i = mSources.size() - 1; i >= 0; i--) { final InsetsSource source = mSources.valueAt(i); - if (source.insetsRoundedCornerFrame()) { + if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) { final Insets insets = source.calculateInsets(roundedCornerFrame, false); roundedCornerFrame.inset(insets); } diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 095189ad03a7..67ac811287cb 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -62,7 +62,6 @@ public class SurfaceControlRegistry { private static class DefaultReporter implements Reporter { public void onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls, int limit, PrintWriter pw) { - final int size = Math.min(surfaceControls.size(), limit); final long now = SystemClock.elapsedRealtime(); final ArrayList<Map.Entry<SurfaceControl, Long>> entries = new ArrayList<>(); for (Map.Entry<SurfaceControl, Long> entry : surfaceControls.entrySet()) { @@ -71,6 +70,7 @@ public class SurfaceControlRegistry { // Sort entries by time registered when dumping // TODO: Or should it sort by name? entries.sort((o1, o2) -> (int) (o1.getValue() - o2.getValue())); + final int size = Math.min(entries.size(), limit); pw.println("SurfaceControlRegistry"); pw.println("----------------------"); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f17ae829f817..36954a9941c4 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7734,13 +7734,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; - final ListenerInfo li = mListenerInfo; + final OnLongClickListener listener = + mListenerInfo == null ? null : mListenerInfo.mOnLongClickListener; boolean shouldPerformHapticFeedback = true; - if (li != null && li.mOnLongClickListener != null) { - handled = li.mOnLongClickListener.onLongClick(View.this); + if (listener != null) { + handled = listener.onLongClick(View.this); if (handled) { - shouldPerformHapticFeedback = - li.mOnLongClickListener.onLongClickUseDefaultHapticFeedback(View.this); + shouldPerformHapticFeedback = listener.onLongClickUseDefaultHapticFeedback( + View.this); } } if (!handled) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e95ba797a985..0e72ea8622e2 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -4244,17 +4244,6 @@ public interface WindowManager extends ViewManager { public InsetsFrameProvider[] providedInsets; /** - * If specified, the frame that used to calculate relative {@link RoundedCorner} will be - * the window frame of this window minus the insets that this window provides. - * - * Task bar will draw fake rounded corners above itself, so we need this insets to calculate - * correct rounded corners for this window. - * - * @hide - */ - public boolean insetsRoundedCornerFrame = false; - - /** * {@link LayoutParams} to be applied to the window when layout with a assigned rotation. * This will make layout during rotation change smoothly. * @@ -4710,7 +4699,6 @@ public interface WindowManager extends ViewManager { out.writeBoolean(mFitInsetsIgnoringVisibility); out.writeBoolean(preferMinimalPostProcessing); out.writeInt(mBlurBehindRadius); - out.writeBoolean(insetsRoundedCornerFrame); out.writeBoolean(mWallpaperTouchEventsEnabled); out.writeTypedArray(providedInsets, 0 /* parcelableFlags */); checkNonRecursiveParams(); @@ -4782,7 +4770,6 @@ public interface WindowManager extends ViewManager { mFitInsetsIgnoringVisibility = in.readBoolean(); preferMinimalPostProcessing = in.readBoolean(); mBlurBehindRadius = in.readInt(); - insetsRoundedCornerFrame = in.readBoolean(); mWallpaperTouchEventsEnabled = in.readBoolean(); providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR); paramsForRotation = in.createTypedArray(LayoutParams.CREATOR); @@ -5090,11 +5077,6 @@ public interface WindowManager extends ViewManager { changes |= LAYOUT_CHANGED; } - if (insetsRoundedCornerFrame != o.insetsRoundedCornerFrame) { - insetsRoundedCornerFrame = o.insetsRoundedCornerFrame; - changes |= LAYOUT_CHANGED; - } - if (paramsForRotation != o.paramsForRotation) { if ((changes & LAYOUT_CHANGED) == 0) { if (paramsForRotation != null && o.paramsForRotation != null @@ -5332,10 +5314,6 @@ public interface WindowManager extends ViewManager { sb.append(prefix).append(" ").append(providedInsets[i]); } } - if (insetsRoundedCornerFrame) { - sb.append(" insetsRoundedCornerFrame="); - sb.append(insetsRoundedCornerFrame); - } if (paramsForRotation != null && paramsForRotation.length != 0) { sb.append(System.lineSeparator()); sb.append(prefix).append(" paramsForRotation:"); diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 62044aa78213..b22910648e71 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -181,6 +181,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { public static final int FLUSH_REASON_VIEW_TREE_APPEARING = 9; /** @hide */ public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10; + /** @hide */ + public static final int FLUSH_REASON_LOGIN_DETECTED = 11; /** * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps @@ -191,20 +193,23 @@ public abstract class ContentCaptureSession implements AutoCloseable { static final long NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS = 258825825L; /** @hide */ - @IntDef(prefix = { "FLUSH_REASON_" }, value = { - FLUSH_REASON_FULL, - FLUSH_REASON_VIEW_ROOT_ENTERED, - FLUSH_REASON_SESSION_STARTED, - FLUSH_REASON_SESSION_FINISHED, - FLUSH_REASON_IDLE_TIMEOUT, - FLUSH_REASON_TEXT_CHANGE_TIMEOUT, - FLUSH_REASON_SESSION_CONNECTED, - FLUSH_REASON_FORCE_FLUSH, - FLUSH_REASON_VIEW_TREE_APPEARING, - FLUSH_REASON_VIEW_TREE_APPEARED - }) + @IntDef( + prefix = {"FLUSH_REASON_"}, + value = { + FLUSH_REASON_FULL, + FLUSH_REASON_VIEW_ROOT_ENTERED, + FLUSH_REASON_SESSION_STARTED, + FLUSH_REASON_SESSION_FINISHED, + FLUSH_REASON_IDLE_TIMEOUT, + FLUSH_REASON_TEXT_CHANGE_TIMEOUT, + FLUSH_REASON_SESSION_CONNECTED, + FLUSH_REASON_FORCE_FLUSH, + FLUSH_REASON_VIEW_TREE_APPEARING, + FLUSH_REASON_VIEW_TREE_APPEARED, + FLUSH_REASON_LOGIN_DETECTED + }) @Retention(RetentionPolicy.SOURCE) - public @interface FlushReason{} + public @interface FlushReason {} private final Object mLock = new Object(); @@ -685,8 +690,10 @@ public abstract class ContentCaptureSession implements AutoCloseable { return "VIEW_TREE_APPEARING"; case FLUSH_REASON_VIEW_TREE_APPEARED: return "VIEW_TREE_APPEARED"; + case FLUSH_REASON_LOGIN_DETECTED: + return "LOGIN_DETECTED"; default: - return "UNKOWN-" + reason; + return "UNKNOWN-" + reason; } } diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java index 044a31f3b297..f218995e55ad 100644 --- a/core/java/android/view/contentcapture/ViewNode.java +++ b/core/java/android/view/contentcapture/ViewNode.java @@ -480,6 +480,11 @@ public final class ViewNode extends AssistStructure.ViewNode { return mLocaleList; } + /** @hide */ + public void setTextIdEntry(@NonNull String textIdEntry) { + mTextIdEntry = textIdEntry; + } + private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) { long nodeFlags = mFlags; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 2dbff581fe84..46f2931b55cd 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -6454,7 +6454,7 @@ public class Editor { @VisibleForTesting public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { final int trueLine = mTextView.getLineAtCoordinate(y); - if (layout == null || prevLine > layout.getLineCount() + if (layout == null || prevLine >= layout.getLineCount() || layout.getLineCount() <= 0 || prevLine < 0) { // Invalid parameters, just return whatever line is at y. return trueLine; diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 52554ee387df..4e20220ec98c 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2598,6 +2598,11 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return VIEW_GROUP_ACTION_ADD_TAG; } + + @Override + public final void visitUris(@NonNull Consumer<Uri> visitor) { + mNestedViews.visitUris(visitor); + } } /** diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index c0370cc5517d..8c05130bf5fe 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -493,6 +493,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } + if ((flags & FLAG_MOVED_TO_TOP) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP"); + } return sb.toString(); } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 7ad2a6898fb7..8135f9cd2f46 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -549,11 +549,6 @@ public final class SystemUiDeviceConfigFlags { "task_manager_inform_job_scheduler_of_pending_app_stop"; /** - * (boolean) Whether to show notification volume control slider separate from ring. - */ - public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification"; - - /** * (boolean) Whether widget provider info would be saved to / loaded from system persistence * layer as opposed to individual manifests in respective apps. */ diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index e530aec2119a..869b69611eba 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -28,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS; @@ -258,8 +259,16 @@ public class InteractionJankMonitor { public static final int CUJ_IME_INSETS_ANIMATION = 69; public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70; public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71; + // 72 - 77 are reserved for b/281564325. - private static final int LAST_CUJ = CUJ_LAUNCHER_OPEN_SEARCH_RESULT; + /** + * In some cases when we do not have any end-target, we play a simple slide-down animation. + * eg: Open an app from Overview/Task switcher such that there is no home-screen icon. + * eg: Exit the app using back gesture. + */ + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78; + + private static final int LAST_CUJ = CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; private static final int NO_STATSD_LOGGING = -1; // Used to convert CujType to InteractionType enum value for statsd logging. @@ -340,6 +349,14 @@ public class InteractionJankMonitor { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; + // 72 - 77 are reserved for b/281564325. + CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING; + CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING; + CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING; + CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING; + CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING; + CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; } private static class InstanceHolder { @@ -439,6 +456,7 @@ public class InteractionJankMonitor { CUJ_IME_INSETS_ANIMATION, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION, CUJ_LAUNCHER_OPEN_SEARCH_RESULT, + CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -1050,6 +1068,8 @@ public class InteractionJankMonitor { return "LOCKSCREEN_CLOCK_MOVE_ANIMATION"; case CUJ_LAUNCHER_OPEN_SEARCH_RESULT: return "LAUNCHER_OPEN_SEARCH_RESULT"; + case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK: + return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index ae58626e49eb..d2564fb9c268 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -157,7 +157,7 @@ oneway interface IStatusBar */ void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver, in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId, - long operationId, String opPackageName, long requestId, int multiSensorConfig); + long operationId, String opPackageName, long requestId); /** * Used to notify the authentication dialog that a biometric has been authenticated. */ diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 370885936211..3977666627b7 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -123,8 +123,7 @@ interface IStatusBarService // Used to show the authentication dialog (Biometrics, Device Credential) void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver, in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, - int userId, long operationId, String opPackageName, long requestId, - int multiSensorConfig); + int userId, long operationId, String opPackageName, long requestId); // Used to notify the authentication dialog that a biometric has been authenticated void onBiometricAuthenticated(int modality); diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index f2776353fd1b..116c301c28f0 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -367,28 +367,42 @@ public class LatencyTracker { * using a single static object. */ @VisibleForTesting + @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public void startListeningForLatencyTrackerConfigChanges() { final Context context = ActivityThread.currentApplication(); - if (context != null - && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) { - // Post initialization to the background in case we're running on the main thread. - BackgroundThread.getHandler().post(() -> this.updateProperties( - DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER))); - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER, - BackgroundThread.getExecutor(), mOnPropertiesChangedListener); - } else { + if (context == null) { if (DEBUG) { - if (context == null) { - Log.d(TAG, "No application for " + ActivityThread.currentActivityThread()); - } else { - synchronized (mLock) { - Log.d(TAG, "Initialized the LatencyTracker." - + " (No READ_DEVICE_CONFIG permission to change configs)" - + " enabled=" + mEnabled + ", package=" + context.getPackageName()); - } - } + Log.d(TAG, "No application for package: " + ActivityThread.currentPackageName()); } + return; } + if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) { + if (DEBUG) { + synchronized (mLock) { + Log.d(TAG, "Initialized the LatencyTracker." + + " (No READ_DEVICE_CONFIG permission to change configs)" + + " enabled=" + mEnabled + ", package=" + context.getPackageName()); + } + } + return; + } + + // Post initialization to the background in case we're running on the main thread. + BackgroundThread.getHandler().post(() -> { + try { + this.updateProperties( + DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)); + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER, + BackgroundThread.getExecutor(), mOnPropertiesChangedListener); + } catch (SecurityException ex) { + // In case of running tests that the main thread passes the check, + // but the background thread doesn't have necessary permissions. + // Swallow it since it's ok to ignore device config changes in the tests. + Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted=" + + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) + + ", package=" + context.getPackageName()); + } + }); } /** diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index f55d15de1d51..fb4b026f0b61 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -163,6 +163,8 @@ public class PointerLocationView extends View implements InputDeviceListener, @UnsupportedAppUsage private boolean mPrintCoords = true; + private float mDensity; + public PointerLocationView(Context c) { super(c); setFocusableInTouchMode(true); @@ -357,19 +359,20 @@ public class PointerLocationView extends View implements InputDeviceListener, // Draw current touch ellipse. mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128); - drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor, - ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint); + drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor * mDensity, + ps.mCoords.touchMinor * mDensity, ps.mCoords.orientation, mPaint); // Draw current tool ellipse. mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel); - drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor, - ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint); + drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor * mDensity, + ps.mCoords.toolMinor * mDensity, ps.mCoords.orientation, mPaint); // Draw the orientation arrow. float arrowSize = ps.mCoords.toolMajor * 0.7f; if (arrowSize < 20) { arrowSize = 20; } + arrowSize *= mDensity; mPaint.setARGB(255, pressureLevel, 255, 0); float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation) * arrowSize); @@ -398,7 +401,7 @@ public class PointerLocationView extends View implements InputDeviceListener, canvas.drawCircle( ps.mCoords.x + orientationVectorX * tiltScale, ps.mCoords.y + orientationVectorY * tiltScale, - 3.0f, mPaint); + 3.0f * mDensity, mPaint); // Draw the current bounding box if (ps.mHasBoundingBox) { @@ -1003,10 +1006,10 @@ public class PointerLocationView extends View implements InputDeviceListener, // Compute size by display density. private void configureDensityDependentFactors() { - final float density = getResources().getDisplayMetrics().density; - mTextPaint.setTextSize(10 * density); - mPaint.setStrokeWidth(1 * density); - mCurrentPointPaint.setStrokeWidth(1 * density); - mPathPaint.setStrokeWidth(1 * density); + mDensity = getResources().getDisplayMetrics().density; + mTextPaint.setTextSize(10 * mDensity); + mPaint.setStrokeWidth(1 * mDensity); + mCurrentPointPaint.setStrokeWidth(1 * mDensity); + mPathPaint.setStrokeWidth(1 * mDensity); } } diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto index 84c82e094dd4..3cedba0632aa 100644 --- a/core/proto/android/os/system_properties.proto +++ b/core/proto/android/os/system_properties.proto @@ -434,9 +434,8 @@ message SystemPropertiesProto { optional string vibrator = 37; optional string virtual_device = 38; optional string vulkan = 39; - optional string egl_legacy = 40; - // Next Tag: 41 + // Next Tag: 40 } optional Hardware hardware = 27; @@ -559,3 +558,4 @@ message SystemPropertiesProto { // Next Tag: 32 } + diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 128de8bf47df..052e2f20a313 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -471,10 +471,6 @@ message GlobalSettingsProto { optional SettingProto updatable_driver_prerelease_opt_in_apps = 18; optional SettingProto angle_egl_features = 19; - // ANGLE - List of Apps that ANGLE may have issues with - optional SettingProto angle_deferlist = 20; - // ANGLE - Integer mode of the logic for applying `angle_deferlist` - optional SettingProto angle_deferlist_mode = 21; } optional Gpu gpu = 59; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c6fa7c5814da..68cfd19ddb5e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1854,6 +1854,11 @@ <item>telephony</item> </string-array> + <!-- The difference in millis that has to exist between a time suggestion under + consideration by the time_detector and the system clock before the system clock will be + changed. --> + <integer name="config_timeDetectorAutoUpdateDiffMillis">2000</integer> + <!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based suggestions to TimeDetector service. See also config_autoTimeSourcesPriority. --> <bool name="config_enableGnssTimeUpdateService">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2451bd16bb4f..38cad788dde5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2241,6 +2241,7 @@ <java-symbol type="string" name="config_persistentDataPackageName" /> <java-symbol type="string" name="config_deviceConfiguratorPackageName" /> <java-symbol type="array" name="config_autoTimeSourcesPriority" /> + <java-symbol type="integer" name="config_timeDetectorAutoUpdateDiffMillis" /> <java-symbol type="bool" name="config_enableGnssTimeUpdateService" /> <java-symbol type="bool" name="config_enableGeolocationTimeZoneDetection" /> <java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneProvider" /> @@ -5136,6 +5137,6 @@ <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" /> <java-symbol type="drawable" name="focus_event_pressed_key_background" /> - <java-symbol type="string" name="config_defaultShutdownVibrationFile" /> + <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" /> </resources> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 1b570dad1904..1516a07f875b 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -33,6 +33,8 @@ import static android.app.Notification.EXTRA_MESSAGING_PERSON; import static android.app.Notification.EXTRA_PEOPLE_LIST; import static android.app.Notification.EXTRA_PICTURE; import static android.app.Notification.EXTRA_PICTURE_ICON; +import static android.app.Notification.EXTRA_SUMMARY_TEXT; +import static android.app.Notification.EXTRA_TITLE; import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI; import static android.app.Notification.MessagingStyle.Message.KEY_SENDER_PERSON; import static android.app.Notification.MessagingStyle.Message.KEY_TEXT; @@ -76,6 +78,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemProperties; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -111,6 +114,9 @@ public class NotificationTest { @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); + // TODO(b/169435530): remove this flag set once resolved. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(false)); } @Test @@ -1502,6 +1508,107 @@ public class NotificationTest { Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second); } + // Ensures that extras in a Notification Builder can be updated. + @Test + public void testExtras_cachedExtrasOverwrittenByUserProvided() { + // Sets the flag to new state. + // TODO(b/169435530): remove this set value once resolved. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(true)); + Bundle extras = new Bundle(); + extras.putCharSequence(EXTRA_TITLE, "test title"); + extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text"); + + Notification.Builder builder = new Notification.Builder(mContext, "test id") + .addExtras(extras); + + Notification notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "test title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + + extras.putCharSequence(EXTRA_TITLE, "new title"); + builder.addExtras(extras); + notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "new title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + } + + // Ensures that extras in a Notification Builder can be updated by an extender. + @Test + public void testExtras_cachedExtrasOverwrittenByExtender() { + // Sets the flag to new state. + // TODO(b/169435530): remove this set value once resolved. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(true)); + Notification.CarExtender extender = new Notification.CarExtender().setColor(1234); + + Notification notification = new Notification.Builder(mContext, "test id") + .extend(extender).build(); + + extender.setColor(5678); + + Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build(); + + Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification); + assertThat(recoveredExtender.getColor()).isEqualTo(5678); + } + + // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated. + // TODO(b/169435530): remove this test once resolved. + @Test + public void testExtras_cachedExtrasOverwrittenByUserProvidedOld() { + // Sets the flag to old state. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(false)); + + Bundle extras = new Bundle(); + extras.putCharSequence(EXTRA_TITLE, "test title"); + extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text"); + + Notification.Builder builder = new Notification.Builder(mContext, "test id") + .addExtras(extras); + + Notification notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "test title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + + extras.putCharSequence(EXTRA_TITLE, "new title"); + builder.addExtras(extras); + notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "test title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + } + + // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated + // by an extender. + // TODO(b/169435530): remove this test once resolved. + @Test + public void testExtras_cachedExtrasOverwrittenByExtenderOld() { + // Sets the flag to old state. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(false)); + + Notification.CarExtender extender = new Notification.CarExtender().setColor(1234); + + Notification notification = new Notification.Builder(mContext, "test id") + .extend(extender).build(); + + extender.setColor(5678); + + Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build(); + + Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification); + assertThat(recoveredExtender.getColor()).isEqualTo(1234); + } + private void assertValid(Notification.Colors c) { // Assert that all colors are populated assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID); diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 6e8e93a8c86b..33ee72d18c3a 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -137,7 +137,7 @@ public class FontScaleConverterActivityTest { ); }); - PollingCheck.waitFor(/* timeout= */ 5000, () -> { + PollingCheck.waitFor(/* timeout= */ 7000, () -> { AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false); rule.getScenario().onActivity(activity -> isActivityAtCorrectScale.set( @@ -146,12 +146,7 @@ public class FontScaleConverterActivityTest { .fontScale == fontScale ) ); - return isActivityAtCorrectScale.get() && InstrumentationRegistry - .getInstrumentation() - .getContext() - .getResources() - .getConfiguration() - .fontScale == fontScale; + return isActivityAtCorrectScale.get(); }); } diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 27d58b8cdcb7..f8ebd09899a8 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -27,6 +27,8 @@ import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; +import com.google.common.collect.ImmutableMap; + import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -37,6 +39,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Map; + /** * Unit tests for {@link ContentCaptureSession}. * @@ -145,6 +149,35 @@ public class ContentCaptureSessionTest { assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(1); } + @Test + public void testGetFlushReasonAsString() { + int invalidFlushReason = ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED + 1; + Map<Integer, String> expectedMap = + new ImmutableMap.Builder<Integer, String>() + .put(ContentCaptureSession.FLUSH_REASON_FULL, "FULL") + .put(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED, "VIEW_ROOT") + .put(ContentCaptureSession.FLUSH_REASON_SESSION_STARTED, "STARTED") + .put(ContentCaptureSession.FLUSH_REASON_SESSION_FINISHED, "FINISHED") + .put(ContentCaptureSession.FLUSH_REASON_IDLE_TIMEOUT, "IDLE") + .put(ContentCaptureSession.FLUSH_REASON_TEXT_CHANGE_TIMEOUT, "TEXT_CHANGE") + .put(ContentCaptureSession.FLUSH_REASON_SESSION_CONNECTED, "CONNECTED") + .put(ContentCaptureSession.FLUSH_REASON_FORCE_FLUSH, "FORCE_FLUSH") + .put( + ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING, + "VIEW_TREE_APPEARING") + .put( + ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED, + "VIEW_TREE_APPEARED") + .put(ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED, "LOGIN_DETECTED") + .put(invalidFlushReason, "UNKOWN-" + invalidFlushReason) + .build(); + + expectedMap.forEach( + (reason, expected) -> + assertThat(ContentCaptureSession.getFlushReasonAsString(reason)) + .isEqualTo(expected)); + } + // Cannot use @Spy because we need to pass the session id on constructor private class MyContentCaptureSession extends ContentCaptureSession { int mInternalNotifyViewTreeEventStartedCount = 0; diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java index 93315f11d242..a4e77f5d8dc5 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java @@ -41,49 +41,60 @@ public class ViewNodeTest { private final Context mContext = InstrumentationRegistry.getTargetContext(); + private final View mView = new View(mContext); + + private final ViewStructureImpl mViewStructure = new ViewStructureImpl(mView); + + private final ViewNode mViewNode = mViewStructure.getNode(); + @Mock private HtmlInfo mHtmlInfoMock; @Test public void testUnsupportedProperties() { - View view = new View(mContext); + mViewStructure.setChildCount(1); + assertThat(mViewNode.getChildCount()).isEqualTo(0); + + mViewStructure.addChildCount(1); + assertThat(mViewNode.getChildCount()).isEqualTo(0); - ViewStructureImpl structure = new ViewStructureImpl(view); - ViewNode node = structure.getNode(); + assertThat(mViewStructure.newChild(0)).isNull(); + assertThat(mViewNode.getChildCount()).isEqualTo(0); - structure.setChildCount(1); - assertThat(node.getChildCount()).isEqualTo(0); + assertThat(mViewStructure.asyncNewChild(0)).isNull(); + assertThat(mViewNode.getChildCount()).isEqualTo(0); - structure.addChildCount(1); - assertThat(node.getChildCount()).isEqualTo(0); + mViewStructure.asyncCommit(); + assertThat(mViewNode.getChildCount()).isEqualTo(0); - assertThat(structure.newChild(0)).isNull(); - assertThat(node.getChildCount()).isEqualTo(0); + mViewStructure.setWebDomain("Y U NO SET?"); + assertThat(mViewNode.getWebDomain()).isNull(); - assertThat(structure.asyncNewChild(0)).isNull(); - assertThat(node.getChildCount()).isEqualTo(0); + assertThat(mViewStructure.newHtmlInfoBuilder("WHATEVER")).isNull(); - structure.asyncCommit(); - assertThat(node.getChildCount()).isEqualTo(0); + mViewStructure.setHtmlInfo(mHtmlInfoMock); + assertThat(mViewNode.getHtmlInfo()).isNull(); - structure.setWebDomain("Y U NO SET?"); - assertThat(node.getWebDomain()).isNull(); + mViewStructure.setDataIsSensitive(true); - assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull(); + assertThat(mViewStructure.getTempRect()).isNull(); - structure.setHtmlInfo(mHtmlInfoMock); - assertThat(node.getHtmlInfo()).isNull(); + // Graphic properties + mViewStructure.setElevation(6.66f); + assertThat(mViewNode.getElevation()).isEqualTo(0f); + mViewStructure.setAlpha(66.6f); + assertThat(mViewNode.getAlpha()).isEqualTo(1.0f); + mViewStructure.setTransformation(Matrix.IDENTITY_MATRIX); + assertThat(mViewNode.getTransformation()).isNull(); + } - structure.setDataIsSensitive(true); + @Test + public void testGetSet_textIdEntry() { + assertThat(mViewNode.getTextIdEntry()).isNull(); - assertThat(structure.getTempRect()).isNull(); + String expected = "TEXT_ID_ENTRY"; + mViewNode.setTextIdEntry(expected); - // Graphic properties - structure.setElevation(6.66f); - assertThat(node.getElevation()).isWithin(1.0e-10f).of(0f); - structure.setAlpha(66.6f); - assertThat(node.getAlpha()).isWithin(1.0e-10f).of(1.0f); - structure.setTransformation(Matrix.IDENTITY_MATRIX); - assertThat(node.getTransformation()).isNull(); + assertThat(mViewNode.getTextIdEntry()).isEqualTo(expected); } } diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 33c44eaaa451..945fb6da5b65 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -719,6 +719,30 @@ public class RemoteViewsTest { } @Test + public void visitUris_nestedViews() { + final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); + + final RemoteViews inner = new RemoteViews(mPackage, 33); + final Uri imageUriI = Uri.parse("content://inner/image"); + final Icon icon1 = Icon.createWithContentUri("content://inner/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://inner/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://inner/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://inner/icon4"); + inner.setImageViewUri(R.id.image, imageUriI); + inner.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + outer.addView(R.id.layout, inner); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + outer.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriI)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test public void visitUris_separateOrientation() { final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); final Uri imageUriL = Uri.parse("content://landscape/image"); diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl index 54f8808e2285..5d7fd85fc178 100644 --- a/data/keyboards/Vendor_0957_Product_0001.kl +++ b/data/keyboards/Vendor_0957_Product_0001.kl @@ -45,6 +45,7 @@ key 11 0 # custom keys key usage 0x000c01BB TV_INPUT +key usage 0x000c0186 MACRO_1 key usage 0x000c0185 TV_TELETEXT key usage 0x000c0061 CAPTIONS @@ -77,4 +78,4 @@ key usage 0x000c009D CHANNEL_DOWN key usage 0x000c0077 BUTTON_3 WAKE #YouTube key usage 0x000c0078 BUTTON_4 WAKE #Netflix key usage 0x000c0079 BUTTON_6 WAKE -key usage 0x000c007A BUTTON_7 WAKE
\ No newline at end of file +key usage 0x000c007A BUTTON_7 WAKE diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 89f4890c254e..3b45d5d88d12 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -1418,7 +1418,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); } - primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer); + primaryContainer.getTaskContainer().addSplitContainer(splitContainer); } /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ @@ -1430,8 +1430,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } final List<SplitContainer> splitsToRemove = new ArrayList<>(); + final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>(); - for (SplitContainer splitContainer : taskContainer.mSplitContainers) { + for (SplitContainer splitContainer : splitContainers) { if (splitContainer.getPrimaryContainer() != container && splitContainer.getSecondaryContainer() != container) { continue; @@ -1449,7 +1450,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } container.resetDependencies(); - taskContainer.mSplitContainers.removeAll(splitsToRemove); + taskContainer.removeSplitContainers(splitsToRemove); // If there is any TaskFragment split with the PIP TaskFragment, update their presentations // since the split is dismissed. // We don't want to close any of them even if they are dependencies of the PIP TaskFragment. @@ -1481,7 +1482,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Cleanup any split references. final List<SplitContainer> containersToRemove = new ArrayList<>(); - for (SplitContainer splitContainer : taskContainer.mSplitContainers) { + final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); + for (SplitContainer splitContainer : splitContainers) { if (containersToRemove.contains(splitContainer)) { // Don't need to check because it has been in the remove list. continue; @@ -1492,7 +1494,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen containersToRemove.add(splitContainer); } } - taskContainer.mSplitContainers.removeAll(containersToRemove); + taskContainer.removeSplitContainers(containersToRemove); // Cleanup any dependent references. for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) { @@ -1629,7 +1631,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Whether the given split is the topmost split in the Task. */ private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) { final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer() - .getTaskContainer().mSplitContainers; + .getTaskContainer().getSplitContainers(); return splitContainer == splitContainers.get(splitContainers.size() - 1); } @@ -1641,7 +1643,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container == null) { return null; } - final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers; + final List<SplitContainer> splitContainers = + container.getTaskContainer().getSplitContainers(); if (splitContainers.isEmpty()) { return null; } @@ -1665,7 +1668,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer) { final List<SplitContainer> splitContainers = firstContainer.getTaskContainer() - .mSplitContainers; + .getSplitContainers(); for (int i = splitContainers.size() - 1; i >= 0; i--) { final SplitContainer splitContainer = splitContainers.get(i); final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); @@ -1945,7 +1948,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") SplitContainer getSplitContainer(@NonNull IBinder token) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers; + final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers(); for (SplitContainer container : containers) { if (container.getToken().equals(token)) { return container; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 4b15bb187035..06265105028b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -55,7 +55,7 @@ class TaskContainer { /** Active split pairs in this Task. */ @NonNull - final List<SplitContainer> mSplitContainers = new ArrayList<>(); + private final List<SplitContainer> mSplitContainers = new ArrayList<>(); @NonNull private final Configuration mConfiguration; @@ -207,6 +207,19 @@ class TaskContainer { return false; } + @NonNull + List<SplitContainer> getSplitContainers() { + return new ArrayList<>(mSplitContainers); + } + + void addSplitContainer(@NonNull SplitContainer splitContainer) { + mSplitContainers.add(splitContainer); + } + + void removeSplitContainers(@NonNull List<SplitContainer> containers) { + mSplitContainers.removeAll(containers); + } + /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */ void getSplitStates(@NonNull List<SplitInfo> outSplitStates) { for (SplitContainer container : mSplitContainers) { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index ff08782e8cd8..82692a5170df 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -320,11 +320,11 @@ public class SplitControllerTest { doReturn(tf).when(splitContainer).getSecondaryContainer(); doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); - final List<SplitContainer> splitContainers = - mSplitController.getTaskContainer(TASK_ID).mSplitContainers; - splitContainers.add(splitContainer); + final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); + taskContainer.addSplitContainer(splitContainer); // Add a mock SplitContainer on top of splitContainer - splitContainers.add(1, mock(SplitContainer.class)); + final SplitContainer splitContainer2 = mock(SplitContainer.class); + taskContainer.addSplitContainer(splitContainer2); mSplitController.updateContainer(mTransaction, tf); @@ -332,7 +332,9 @@ public class SplitControllerTest { // Verify if one or both containers in the top SplitContainer are finished, // dismissPlaceholder() won't be called. - splitContainers.remove(1); + final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>(); + splitContainersToRemove.add(splitContainer2); + taskContainer.removeSplitContainers(splitContainersToRemove); doReturn(true).when(tf).isFinished(); mSplitController.updateContainer(mTransaction, tf); @@ -377,7 +379,7 @@ public class SplitControllerTest { doReturn(true).when(taskContainer).isVisible(); mSplitController.updateContainer(mTransaction, taskFragmentContainer); - verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0), + verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0), mTransaction); } @@ -1091,7 +1093,7 @@ public class SplitControllerTest { verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken()); verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken()); assertTrue(taskContainer.mContainers.isEmpty()); - assertTrue(taskContainer.mSplitContainers.isEmpty()); + assertTrue(taskContainer.getSplitContainers().isEmpty()); } @Test diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index 171a6b2fe5fb..6f8a7666e5a8 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -30,6 +30,8 @@ <color name="bubbles_light">#FFFFFF</color> <color name="bubbles_dark">@color/GM2_grey_800</color> <color name="bubbles_icon_tint">@color/GM2_grey_700</color> + <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color> + <color name="bubble_bar_expanded_view_handle_dark">#99000000</color> <!-- PiP --> <color name="pip_custom_close_bg">#D93025</color> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 2be34c90a661..aa05179ed113 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -229,7 +229,11 @@ <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. --> <dimen name="bubblebar_size">72dp</dimen> <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. --> - <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen> + <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen> + <!-- The width of the drag handle shown along with a bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen> + <!-- The height of the drag handle shown along with a bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen> <!-- Bottom and end margin for compat buttons. --> <dimen name="compat_button_margin">24dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index fbdbd3e61d92..7b37d5947f17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; +import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; @@ -111,6 +112,11 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { return false; } + final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); + if (options != null && options.getType() == ANIM_SCENE_TRANSITION) { + // Scene-transition will be handled by app side. + return false; + } // Start ActivityEmbedding animation. mTransitionCallbacks.put(transition, finishCallback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 3eb9fa2eef6b..988ad8c6c0f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1846,7 +1846,7 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView != null) { mStackView.setVisibility(VISIBLE); } - if (mLayerView != null && isStackExpanded()) { + if (mLayerView != null) { mLayerView.setVisibility(VISIBLE); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index a48be5ed5ad5..91c7cc0c6e89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1346,7 +1346,7 @@ public class BubbleStackView extends FrameLayout // Recreates & shows the education views. Call when a theme/config change happens. private void updateUserEdu() { - if (isStackEduVisible()) { + if (isStackEduVisible() && !mStackEduView.isHiding()) { removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); addView(mStackEduView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index 627273f093f3..d0598cd28582 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -37,8 +37,7 @@ class StackEducationView constructor( context: Context, positioner: BubblePositioner, controller: BubbleController -) - : LinearLayout(context) { +) : LinearLayout(context) { private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView" else BubbleDebugConfig.TAG_BUBBLES @@ -53,7 +52,8 @@ class StackEducationView constructor( private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) } private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) } - private var isHiding = false + var isHiding = false + private set init { LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index b8f049becb6f..da1a5572e53b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles.bar; +import android.annotation.ColorInt; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.res.TypedArray; @@ -46,10 +48,10 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private BubbleController mController; private BubbleTaskViewHelper mBubbleTaskViewHelper; - private HandleView mMenuView; - private TaskView mTaskView; + private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext()); + private @Nullable TaskView mTaskView; - private int mMenuHeight; + private int mHandleHeight; private int mBackgroundColor; private float mCornerRadius = 0f; @@ -83,11 +85,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView super.onFinishInflate(); Context context = getContext(); setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation)); - mMenuHeight = context.getResources().getDimensionPixelSize( - R.dimen.bubblebar_expanded_view_menu_size); - mMenuView = new HandleView(context); - addView(mMenuView); - + mHandleHeight = context.getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_size); + addView(mHandleView); applyThemeAttrs(); setClipToOutline(true); setOutlineProvider(new ViewOutlineProvider() { @@ -114,7 +114,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView void applyThemeAttrs() { boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources()); - final TypedArray ta = mContext.obtainStyledAttributes(new int[] { + final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ android.R.attr.dialogCornerRadius, android.R.attr.colorBackgroundFloating}); mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; @@ -123,14 +123,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView ta.recycle(); - mMenuView.setCornerRadius(mCornerRadius); - mMenuHeight = getResources().getDimensionPixelSize( - R.dimen.bubblebar_expanded_view_menu_size); + mHandleHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_size); if (mTaskView != null) { mTaskView.setCornerRadius(mCornerRadius); - mTaskView.setElevation(150); - updateMenuColor(); + updateHandleAndBackgroundColor(true /* animated */); } } @@ -138,10 +136,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - - // Add corner radius here so that the menu extends behind the rounded corners of TaskView. - int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height); - measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, + int menuViewHeight = Math.min(mHandleHeight, height); + measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, MeasureSpec.getMode(heightMeasureSpec))); if (mTaskView != null) { @@ -153,12 +149,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); // Drag handle above - final int dragHandleBottom = t + mMenuView.getMeasuredHeight(); - mMenuView.layout(l, t, r, dragHandleBottom); + final int dragHandleBottom = t + mHandleView.getMeasuredHeight(); + mHandleView.layout(l, t, r, dragHandleBottom); if (mTaskView != null) { - // Subtract radius so that the menu extends behind the rounded corners of TaskView. - mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r, + mTaskView.layout(l, dragHandleBottom, r, dragHandleBottom + mTaskView.getMeasuredHeight()); } } @@ -166,7 +162,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override public void onTaskCreated() { setContentVisibility(true); - updateMenuColor(); + updateHandleAndBackgroundColor(false /* animated */); } @Override @@ -218,16 +214,33 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } } - /** Updates the menu bar to be the status bar color specified by the app. */ - private void updateMenuColor() { + /** + * Updates the background color to match with task view status/bg color, and sets handle color + * to contrast with the background + */ + private void updateHandleAndBackgroundColor(boolean animated) { if (mTaskView == null) return; - ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo(); - final int taskBgColor = info.taskDescription.getStatusBarColor(); - final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); - if (color != -1) { - mMenuView.setBackgroundColor(color); + final int color = getTaskViewColor(); + final boolean isRegionDark = Color.luminance(color) <= 0.5; + mHandleView.updateHandleColor(isRegionDark, animated); + setBackgroundColor(color); + } + + /** + * Retrieves task view status/nav bar color or background if available + * + * TODO (b/283075226): Update with color sampling when + * RegionSamplingHelper or alternative is available + */ + private @ColorInt int getTaskViewColor() { + if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor; + ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription; + if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) { + return taskDescription.getStatusBarColor(); + } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) { + return taskDescription.getBackgroundColor(); } else { - mMenuView.setBackgroundColor(mBackgroundColor); + return mBackgroundColor; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java new file mode 100644 index 000000000000..e121aa45469d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -0,0 +1,115 @@ +/* + * 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.bubbles.bar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Outline; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.ColorInt; +import androidx.core.content.ContextCompat; + +import com.android.wm.shell.R; + +/** + * Handle view to show at the top of a bubble bar expanded view. + */ +public class BubbleBarHandleView extends View { + private static final long COLOR_CHANGE_DURATION = 120; + + private final int mHandleWidth; + private final int mHandleHeight; + private final @ColorInt int mHandleLightColor; + private final @ColorInt int mHandleDarkColor; + private @Nullable ObjectAnimator mColorChangeAnim; + + public BubbleBarHandleView(Context context) { + this(context, null); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Resources resources = context.getResources(); + mHandleWidth = resources.getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_width); + mHandleHeight = resources.getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_height); + mHandleLightColor = ContextCompat.getColor(context, + R.color.bubble_bar_expanded_view_handle_light); + mHandleDarkColor = ContextCompat.getColor(context, + R.color.bubble_bar_expanded_view_handle_dark); + + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + final int handleCenterX = view.getWidth() / 2; + final int handleCenterY = view.getHeight() / 2; + final float handleRadius = mHandleHeight / 2f; + Rect handleBounds = new Rect( + handleCenterX - mHandleWidth / 2, + handleCenterY - mHandleHeight / 2, + handleCenterX + mHandleWidth / 2, + handleCenterY + mHandleHeight / 2); + outline.setRoundRect(handleBounds, handleRadius); + } + }); + } + + /** + * Updates the handle color. + * + * @param isRegionDark Whether the background behind the handle is dark, and thus the handle + * should be light (and vice versa). + * @param animated Whether to animate the change, or apply it immediately. + */ + public void updateHandleColor(boolean isRegionDark, boolean animated) { + int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor; + if (mColorChangeAnim != null) { + mColorChangeAnim.cancel(); + } + if (animated) { + mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor); + mColorChangeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mColorChangeAnim = null; + } + }); + mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION); + mColorChangeAnim.start(); + } else { + setBackgroundColor(newColor); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java deleted file mode 100644 index 9ee8a9d98aa1..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java +++ /dev/null @@ -1,42 +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 com.android.wm.shell.bubbles.bar; - -import android.content.Context; -import android.view.Gravity; -import android.widget.LinearLayout; - -/** - * Handle / menu view to show at the top of a bubble bar expanded view. - */ -public class HandleView extends LinearLayout { - - // TODO(b/273307221): implement the manage menu in this view. - public HandleView(Context context) { - super(context); - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.CENTER); - } - - /** - * The menu extends past the top of the TaskView because of the rounded corners. This means - * to center content in the menu we must subtract the radius (i.e. the amount of space covered - * by TaskView). - */ - public void setCornerRadius(float radius) { - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java index ac6e4c2a6521..53683c67d825 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java @@ -54,14 +54,6 @@ public class TabletopModeController implements DevicePostureController.OnDevicePostureChangedListener, DisplayController.OnDisplaysChangedListener { /** - * When {@code true}, floating windows like PiP would auto move to the position - * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode. - */ - private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP = - SystemProperties.getBoolean( - "persist.wm.debug.enable_move_floating_window_in_tabletop", true); - - /** * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled, * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise. * See also {@link #getPreferredHalfInTabletopMode()}. @@ -162,14 +154,6 @@ public class TabletopModeController implements } } - /** - * @return {@code true} if floating windows like PiP would auto move to the position - * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode. - */ - public boolean enableMoveFloatingWindowInTabletop() { - return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP; - } - /** @return Preferred half for floating windows like PiP when in tabletop mode. */ @PreferredTabletopHalf public int getPreferredHalfInTabletopMode() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 69f0bad4fb45..7f362f3e2ab7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -222,7 +222,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { for (int i = insetsState.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = insetsState.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { mTempRect.inset(source.calculateVisibleInsets(mTempRect)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 753dfa7396f8..a9ccdf6a156f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -293,6 +293,9 @@ public class SplitDecorManager extends WindowlessWindowManager { } if (mResizingIconView == null) { + if (mRunningAnimationCount == 0 && animFinishedCallback != null) { + animFinishedCallback.accept(false); + } return; } @@ -311,6 +314,9 @@ public class SplitDecorManager extends WindowlessWindowManager { releaseDecor(finishT); finishT.apply(); finishT.close(); + if (mRunningAnimationCount == 0 && animFinishedCallback != null) { + animFinishedCallback.accept(true); + } } }); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java index 65a12d629c5a..2590cab9ff2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java @@ -252,13 +252,16 @@ public class PipMediaController { // It can be removed when min_sdk of the app is set to 31 or greater. @SuppressLint("NewApi") private List<RemoteAction> getMediaActions() { - if (mMediaController == null || mMediaController.getPlaybackState() == null) { + // Cache the PlaybackState since it's a Binder call. + final PlaybackState playbackState; + if (mMediaController == null + || (playbackState = mMediaController.getPlaybackState()) == null) { return Collections.emptyList(); } ArrayList<RemoteAction> mediaActions = new ArrayList<>(); - boolean isPlaying = mMediaController.getPlaybackState().isActive(); - long actions = mMediaController.getPlaybackState().getActions(); + boolean isPlaying = playbackState.isActive(); + long actions = playbackState.getActions(); // Prev action mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index bfc1fb905ade..3c7ce3c32a88 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -249,11 +249,6 @@ public class PipTransition extends PipTransitionController { finishTransaction); } - // Fade in the fadeout PIP when the fixed rotation is finished. - if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) { - fadeExistingPip(true /* show */); - } - return false; } @@ -1056,6 +1051,12 @@ public class PipTransition extends PipTransitionController { .crop(finishTransaction, leash, destBounds) .round(finishTransaction, leash, isInPip) .shadow(finishTransaction, leash, isInPip); + // Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will + // be handled by onFixedRotationFinished(). + if (isInPip && mHasFadeOut) { + startTransaction.setAlpha(leash, 0f); + finishTransaction.setAlpha(leash, 0f); + } } /** Hides and shows the existing PIP during fixed rotation transition of other activities. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java index ed8dc7ded654..fc674a8aa59b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java @@ -65,9 +65,18 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac } Rect pipBounds = new Rect(startingBounds); - // move PiP towards corner if user hasn't moved it manually or the flag is on - if (mKeepClearAreaGravityEnabled - || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) { + boolean shouldApplyGravity = false; + // if PiP is outside of screen insets, reposition using gravity + if (!insets.contains(pipBounds)) { + shouldApplyGravity = true; + } + // if user has not interacted with PiP, reposition using gravity + if (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip()) { + shouldApplyGravity = true; + } + + // apply gravity that will position PiP in bottom left or bottom right corner within insets + if (mKeepClearAreaGravityEnabled || shouldApplyGravity) { float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds); int verticalGravity = Gravity.BOTTOM; int horizontalGravity; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 63181da46b02..6a861ce97431 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -677,7 +677,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> { - if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return; final String tag = "tabletop-mode"; if (!isInTabletopMode) { mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 5c9709c756f7..f35eda6caef0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -371,7 +371,8 @@ public class RecentTasksController implements TaskStackListenerCallback, * Find the background task that match the given component. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) { + public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName, + int userId) { if (componentName == null) { return null; } @@ -383,7 +384,7 @@ public class RecentTasksController implements TaskStackListenerCallback, if (task.isVisible) { continue; } - if (componentName.equals(task.baseIntent.getComponent())) { + if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) { return task; } } 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 34701f1db7b7..ea33a1f1b56d 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 @@ -723,7 +723,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, // in the background with priority. final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional .map(recentTasks -> recentTasks.findTaskInBackground( - intent.getIntent().getComponent())) + intent.getIntent().getComponent(), userId1)) .orElse(null); if (taskInfo != null) { startTask(taskInfo.taskId, position, options); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 14ea86a8c0e9..d2b0e2800dc9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -87,6 +87,14 @@ class SplitScreenTransitions { mStageCoordinator = stageCoordinator; } + private void initTransition(@NonNull IBinder transition, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + mAnimatingTransition = transition; + mFinishTransaction = finishTransaction; + mFinishCallback = finishCallback; + } + /** Play animation for enter transition or dismiss transition. */ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -94,9 +102,7 @@ class SplitScreenTransitions { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { - mFinishCallback = finishCallback; - mAnimatingTransition = transition; - mFinishTransaction = finishTransaction; + initTransition(transition, finishTransaction, finishCallback); final TransitSession pendingTransition = getPendingTransition(transition); if (pendingTransition != null) { @@ -220,6 +226,45 @@ class SplitScreenTransitions { onFinish(null /* wct */, null /* wctCB */); } + /** Play animation for drag divider dismiss transition. */ + void playDragDismissAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor, + @NonNull WindowContainerToken topRoot) { + initTransition(transition, finishTransaction, finishCallback); + + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = change.getLeash(); + + if (toTopRoot.equals(change.getContainer())) { + startTransaction.setAlpha(leash, 1.f); + startTransaction.show(leash); + + ValueAnimator va = new ValueAnimator(); + mAnimations.add(va); + + toTopDecor.onResized(startTransaction, animated -> { + mAnimations.remove(va); + if (animated) { + mTransitions.getMainExecutor().execute(() -> { + onFinish(null /* wct */, null /* wctCB */); + }); + } + }); + } else if (topRoot.equals(change.getContainer())) { + // Ensure it on top of all changes in transition. + startTransaction.setLayer(leash, Integer.MAX_VALUE); + startTransaction.setAlpha(leash, 1.f); + startTransaction.show(leash); + } + } + startTransaction.apply(); + onFinish(null /* wct */, null /* wctCB */); + } + /** Play animation for resize transition. */ void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -227,9 +272,7 @@ class SplitScreenTransitions { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) { - mFinishCallback = finishCallback; - mAnimatingTransition = transition; - mFinishTransaction = finishTransaction; + initTransition(transition, finishTransaction, finishCallback); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index bf20567834e0..e0ffffffa727 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1328,8 +1328,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mIsExiting = true; childrenToTop.resetBounds(wct); wct.reorder(childrenToTop.mRootTaskInfo.token, true); - wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token, - SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); } wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false /* reparentLeafTaskIfRelaunch */); @@ -1517,6 +1515,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction t) { mSplitLayout.update(t); + mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash, + getMainStageBounds()); + mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash, + getSideStageBounds()); setDividerVisibility(true, t); // Ensure divider surface are re-parented back into the hierarchy at the end of the // transition. See Transition#buildFinishTransaction for more detail. @@ -1989,13 +1991,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final boolean mainStageToTop = bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; + final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage; if (!ENABLE_SHELL_TRANSITIONS) { - exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, reason); + exitSplitScreen(toTopStage, reason); return; } final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); + toTopStage.resetBounds(wct); prepareExitSplitScreen(dismissTop, wct); if (mRootTaskInfo != null) { wct.setDoNotPip(mRootTaskInfo.token); @@ -2531,8 +2535,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, shouldAnimate = startPendingEnterAnimation( mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { + final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; shouldAnimate = startPendingDismissAnimation( - mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); + dismiss, info, startTransaction, finishTransaction); + if (shouldAnimate && dismiss.mReason == EXIT_REASON_DRAG_DIVIDER) { + final StageTaskListener toTopStage = + dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage; + mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction, + finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token, + toTopStage.getSplitDecorManager(), mRootTaskInfo.token); + return true; + } } else if (mSplitTransitions.isPendingResize(transition)) { mSplitTransitions.playResizeAnimation(transition, info, startTransaction, finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token, @@ -2787,6 +2800,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.mPendingDismiss = null; return false; } + dismissTransition.setFinishedCallback((callbackWct, callbackT) -> { + mMainStage.getSplitDecorManager().release(callbackT); + mSideStage.getSplitDecorManager().release(callbackT); + }); addDividerBarToTransition(info, false /* show */); return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index e2e9270cd6cc..da7d18641a97 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -19,6 +19,7 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; @@ -201,7 +202,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. - if (mRootTaskInfo.isVisible != taskInfo.isVisible) { + if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { if (taskInfo.isVisible) { mSplitDecorManager.inflate(mContext, mRootLeash, taskInfo.configuration.windowConfiguration.getBounds()); @@ -385,6 +386,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void resetBounds(WindowContainerTransaction wct) { wct.setBounds(mRootTaskInfo.token, null); wct.setAppBounds(mRootTaskInfo.token, null); + wct.setSmallestScreenWidthDp(mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); } void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index f33b0778a1b2..3b306e793640 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -191,6 +191,12 @@ public class Transitions implements RemoteCallable<Transitions>, */ private static final int SYNC_ALLOWANCE_MS = 120; + /** + * Keyguard gets a more generous timeout to finish its animations, because we are always holding + * a sleep token during occlude/unocclude transitions and we want them to finish playing cleanly + */ + private static final int SYNC_ALLOWANCE_KEYGUARD_MS = 2000; + /** For testing only. Disables the force-finish timeout on sync. */ private boolean mDisableForceSync = false; @@ -492,6 +498,10 @@ public class Transitions implements RemoteCallable<Transitions>, finishT.show(leash); } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { finishT.hide(leash); + } else if (isOpening && mode == TRANSIT_CHANGE) { + // Just in case there is a race with another animation (eg. recents finish()). + // Changes are visible->visible so it's a problem if it isn't visible. + t.show(leash); } } } @@ -669,7 +679,7 @@ public class Transitions implements RemoteCallable<Transitions>, // Sleep starts a process of forcing all prior transitions to finish immediately ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Start finish-for-sync track %d", i); - finishForSync(i, null /* forceFinish */); + finishForSync(active, i, null /* forceFinish */); } if (hadPreceding) { return false; @@ -1017,6 +1027,9 @@ public class Transitions implements RemoteCallable<Transitions>, for (int i = 0; i < mPendingTransitions.size(); ++i) { if (mPendingTransitions.get(i).mToken == token) return true; } + for (int i = 0; i < mReadyDuringSync.size(); ++i) { + if (mReadyDuringSync.get(i).mToken == token) return true; + } for (int t = 0; t < mTracks.size(); ++t) { final Track tr = mTracks.get(t); for (int i = 0; i < tr.mReadyTransitions.size(); ++i) { @@ -1103,10 +1116,17 @@ public class Transitions implements RemoteCallable<Transitions>, * * This is then repeated until there are no more pending sleep transitions. * + * @param reason The SLEEP transition that triggered this round of finishes. We will continue + * looping round finishing transitions as long as this is still waiting. * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge * signal to -- so it will be force-finished if it's still running. */ - private void finishForSync(int trackIdx, @Nullable ActiveTransition forceFinish) { + private void finishForSync(ActiveTransition reason, + int trackIdx, @Nullable ActiveTransition forceFinish) { + if (!isTransitionKnown(reason.mToken)) { + Log.d(TAG, "finishForSleep: already played sync transition " + reason); + return; + } final Track track = mTracks.get(trackIdx); if (forceFinish != null) { final Track trk = mTracks.get(forceFinish.getTrack()); @@ -1150,8 +1170,11 @@ public class Transitions implements RemoteCallable<Transitions>, if (track.mActiveTransition == playing) { if (!mDisableForceSync) { // Give it a short amount of time to process it before forcing. - mMainExecutor.executeDelayed(() -> finishForSync(trackIdx, playing), - SYNC_ALLOWANCE_MS); + final int tolerance = KeyguardTransitionHandler.handles(playing.mInfo) + ? SYNC_ALLOWANCE_KEYGUARD_MS + : SYNC_ALLOWANCE_MS; + mMainExecutor.executeDelayed( + () -> finishForSync(reason, trackIdx, playing), tolerance); } break; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java index f81fc6fbea49..6bba0d1386fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java @@ -110,7 +110,7 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { return source; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index a4cf149cc3b5..21994a997be5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -50,11 +50,11 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; +import dagger.Lazy; + import java.util.Optional; import java.util.concurrent.Executor; -import dagger.Lazy; - /** * This helper class contains logic that calculates scaling and cropping parameters * for the folding/unfolding animation. As an input it receives TaskInfo objects and @@ -149,7 +149,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { return source; } } diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index b6696c70dbb1..78a2a38ee3af 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -23,14 +23,33 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "WMShellFlickerTests", +filegroup { + name: "WMShellFlickerTestsBase-src", + srcs: ["src/com/android/wm/shell/flicker/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsBubbles-src", + srcs: ["src/**/bubble/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsPip-src", + srcs: ["src/**/pip/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreen-src", srcs: [ - "src/**/*.java", - "src/**/*.kt", + "src/**/splitscreen/*.kt", + "src/**/splitscreen/benchmark/*.kt", ], - manifest: "AndroidManifest.xml", - test_config: "AndroidTest.xml", +} + +java_defaults { + name: "WMShellFlickerTestsDefault", + manifest: "manifests/AndroidManifest.xml", + test_config_template: "AndroidTestTemplate.xml", platform_apis: true, certificate: "platform", optimize: { @@ -40,16 +59,69 @@ android_test { libs: ["android.test.runner"], static_libs: [ "androidx.test.ext.junit", + "flickertestapplib", "flickerlib", - "flickerlib-apphelpers", "flickerlib-helpers", - "truth-prebuilt", - "app-helpers-core", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", "launcher-helper-lib", "launcher-aosp-tapl", - "wm-flicker-common-assertions", - "wm-flicker-common-app-helpers", - "platform-test-annotations", - "flickertestapplib", + ], + data: [ + ":FlickerTestApp", + ], +} + +android_test { + name: "WMShellFlickerTestsOther", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestOther.xml"], + package_name: "com.android.wm.shell.flicker", + instrumentation_target_package: "com.android.wm.shell.flicker", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + exclude_srcs: [ + ":WMShellFlickerTestsBubbles-src", + ":WMShellFlickerTestsPip-src", + ":WMShellFlickerTestsSplitScreen-src", + ], +} + +android_test { + name: "WMShellFlickerTestsBubbles", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestBubbles.xml"], + package_name: "com.android.wm.shell.flicker.bubbles", + instrumentation_target_package: "com.android.wm.shell.flicker.bubbles", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsBubbles-src", + ], +} + +android_test { + name: "WMShellFlickerTestsPip", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestPip.xml"], + package_name: "com.android.wm.shell.flicker.pip", + instrumentation_target_package: "com.android.wm.shell.flicker.pip", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsPip-src", + ], +} + +android_test { + name: "WMShellFlickerTestsSplitScreen", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"], + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsSplitScreen-src", ], } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml index b5937ae80f0a..8818aa205266 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -1,8 +1,20 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - * Copyright 2020 Google Inc. All Rights Reserved. - --> -<configuration description="Runs WindowManager Shell Flicker Tests"> + ~ 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 WindowManager Shell Flicker Tests {MODULE}"> <option name="test-tag" value="FlickerTests" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> @@ -36,11 +48,11 @@ </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> - <option name="test-file-name" value="WMShellFlickerTests.apk"/> + <option name="test-file-name" value="{MODULE}.apk"/> <option name="test-file-name" value="FlickerTestApp.apk" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.wm.shell.flicker"/> + <option name="package" value="{PACKAGE}"/> <option name="shell-timeout" value="6600s" /> <option name="test-timeout" value="6000s" /> <option name="hidden-api-checks" value="false" /> diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml index 4721741611cf..4721741611cf 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml new file mode 100644 index 000000000000..437871f1bb8f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.bubble"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.bubble" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml new file mode 100644 index 000000000000..cf642f63a41d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml new file mode 100644 index 000000000000..5a8155a66d30 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.pip"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.pip" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml new file mode 100644 index 000000000000..887d8db3042f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.splitscreen"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.splitscreen" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java new file mode 100644 index 000000000000..d38b848fbb4d --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java @@ -0,0 +1,67 @@ +/* + * 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.bubbles.bar; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.graphics.drawable.ColorDrawable; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.core.content.ContextCompat; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class BubbleBarHandleViewTest extends ShellTestCase { + private BubbleBarHandleView mHandleView; + + @Before + public void setup() { + mHandleView = new BubbleBarHandleView(mContext); + } + + @Test + public void testUpdateHandleColor_lightBg() { + mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */); + + assertTrue(mHandleView.getClipToOutline()); + assertTrue(mHandleView.getBackground() instanceof ColorDrawable); + ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); + assertEquals(bgDrawable.getColor(), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark)); + } + + @Test + public void testUpdateHandleColor_darkBg() { + mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */); + + assertTrue(mHandleView.getClipToOutline()); + assertTrue(mHandleView.getBackground() instanceof ColorDrawable); + ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); + assertEquals(bgDrawable.getColor(), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 9189d3dd0327..fb17d8799bda 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -223,7 +223,7 @@ public class SplitScreenControllerTests extends ShellTestCase { doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); - doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any()); + doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); @@ -247,7 +247,7 @@ public class SplitScreenControllerTests extends ShellTestCase { SPLIT_POSITION_BOTTOM_OR_RIGHT); // Put the same component into a task in the background doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks) - .findTaskInBackground(any()); + .findTaskInBackground(any(), anyInt()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index ae69b3ddd042..4e446c684d86 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -44,11 +44,15 @@ public class SplitTestUtils { static SplitLayout createMockSplitLayout() { final Rect dividerBounds = new Rect(48, 0, 52, 100); + final Rect bounds1 = new Rect(0, 0, 40, 100); + final Rect bounds2 = new Rect(60, 0, 100, 100); final SurfaceControl leash = createMockSurface(); SplitLayout out = mock(SplitLayout.class); doReturn(dividerBounds).when(out).getDividerBounds(); doReturn(dividerBounds).when(out).getRefDividerBounds(); doReturn(leash).when(out).getDividerLeash(); + doReturn(bounds1).when(out).getBounds1(); + doReturn(bounds2).when(out).getBounds2(); return out; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 80384531e9ae..60c0e5535568 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -42,6 +42,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import android.annotation.NonNull; import android.app.ActivityManager; @@ -72,6 +73,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.transition.Transitions; @@ -117,13 +119,13 @@ public class SplitTransitionTests extends ShellTestCase { doReturn(mockExecutor).when(mTransitions).getAnimExecutor(); doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire(); mSplitLayout = SplitTestUtils.createMockSplitLayout(); - mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( + mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider); + mIconProvider)); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); - mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( + mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider); + mIconProvider)); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, @@ -137,6 +139,8 @@ public class SplitTransitionTests extends ShellTestCase { .setParentTaskId(mMainStage.mRootTaskInfo.taskId).build(); mSideChild = new TestRunningTaskInfoBuilder() .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build(); + doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager(); + doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager(); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 6621ab8ce0d8..66b6c62f1dd6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -68,6 +68,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; import com.android.wm.shell.sysui.ShellController; @@ -145,6 +146,8 @@ public class StageCoordinatorTests extends ShellTestCase { mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); + doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager(); + doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager(); } @Test diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp index ca1312e75f4c..21f4ca79b68c 100644 --- a/libs/hwui/MemoryPolicy.cpp +++ b/libs/hwui/MemoryPolicy.cpp @@ -28,7 +28,10 @@ namespace android::uirenderer { constexpr static MemoryPolicy sDefaultMemoryPolicy; constexpr static MemoryPolicy sPersistentOrSystemPolicy{ .contextTimeout = 10_s, + .minimumResourceRetention = 1_s, + .maximumResourceRetention = 10_s, .useAlternativeUiHidden = true, + .purgeScratchOnly = false, }; constexpr static MemoryPolicy sLowRamPolicy{ .useAlternativeUiHidden = true, diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h index 347daf34f52a..e10dda990dec 100644 --- a/libs/hwui/MemoryPolicy.h +++ b/libs/hwui/MemoryPolicy.h @@ -53,6 +53,8 @@ struct MemoryPolicy { // The minimum amount of time to hold onto items in the resource cache // The actual time used will be the max of this & when frames were actually rendered nsecs_t minimumResourceRetention = 10_s; + // The maximum amount of time to hold onto items in the resource cache + nsecs_t maximumResourceRetention = 100000_s; // If false, use only TRIM_UI_HIDDEN to drive background cache limits; // If true, use all signals (such as all contexts are stopped) to drive the limits bool useAlternativeUiHidden = true; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index babce88b8e1e..8f81dbad2320 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -277,12 +277,13 @@ void CacheManager::onThreadIdle() { const nsecs_t now = systemTime(CLOCK_MONOTONIC); // Rate limiting - if ((now - mLastDeferredCleanup) < 25_ms) { + if ((now - mLastDeferredCleanup) > 25_ms) { mLastDeferredCleanup = now; const nsecs_t frameCompleteNanos = mFrameCompletions[0]; const nsecs_t frameDiffNanos = now - frameCompleteNanos; const nsecs_t cleanupMillis = - ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention)); + ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention, + mMemoryPolicy.maximumResourceRetention)); mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis), mMemoryPolicy.purgeScratchOnly); } diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 2541a506760c..32680dae75b9 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -3181,7 +3181,10 @@ final public class MediaCodec { mValid = false; mNativeContext = 0; } - sPool.offer(this); + + if (!mInternal) { + sPool.offer(this); + } } private native void native_recycle(); @@ -3245,6 +3248,7 @@ final public class MediaCodec { mNativeContext = context; mMappable = isMappable; mValid = (context != 0); + mInternal = true; } private static final BlockingQueue<LinearBlock> sPool = @@ -3255,6 +3259,7 @@ final public class MediaCodec { private boolean mMappable = false; private ByteBuffer mMapped = null; private long mNativeContext = 0; + private boolean mInternal = false; } /** diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index f614b1720ee4..62af39ffcf98 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -88,6 +88,8 @@ public final class MediaRouter2 { private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList<RouteListingPreferenceCallbackRecord> + mListingPreferenceCallbackRecords = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<TransferCallbackRecord> mTransferCallbackRecords = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<ControllerCallbackRecord> mControllerCallbackRecords = @@ -384,6 +386,43 @@ public final class MediaRouter2 { } /** + * Registers callback to be invoked when the {@link RouteListingPreference} of the target + * router changes. + * + * <p>Calls using a previously registered callback will overwrite the callback record. + * + * @see #setRouteListingPreference(RouteListingPreference) + * @hide + */ + public void registerRouteListingPreferenceCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) { + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(routeListingPreferenceCallback, "callback must not be null"); + + RouteListingPreferenceCallbackRecord record = + new RouteListingPreferenceCallbackRecord(executor, routeListingPreferenceCallback); + + mListingPreferenceCallbackRecords.remove(record); + mListingPreferenceCallbackRecords.add(record); + } + + /** + * Unregisters the given callback to not receive {@link RouteListingPreference} change events. + * + * @hide + */ + public void unregisterRouteListingPreferenceCallback( + @NonNull RouteListingPreferenceCallback callback) { + Objects.requireNonNull(callback, "callback must not be null"); + + if (!mListingPreferenceCallbackRecords.remove( + new RouteListingPreferenceCallbackRecord(/* executor */ null, callback))) { + Log.w(TAG, "unregisterRouteListingPreferenceCallback: Ignoring an unknown callback"); + } + } + + /** * Shows the system output switcher dialog. * * <p>Should only be called when the context of MediaRouter2 is in the foreground and visible on @@ -453,6 +492,20 @@ public final class MediaRouter2 { } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } + notifyRouteListingPreferenceUpdated(routeListingPreference); + } + } + + /** + * Returns the current {@link RouteListingPreference} of the target router. + * + * @see #setRouteListingPreference(RouteListingPreference) + * @hide + */ + @Nullable + public RouteListingPreference getRouteListingPreference() { + synchronized (mLock) { + return mRouteListingPreference; } } @@ -1078,6 +1131,15 @@ public final class MediaRouter2 { } } + private void notifyRouteListingPreferenceUpdated(@Nullable RouteListingPreference preference) { + for (RouteListingPreferenceCallbackRecord record : mListingPreferenceCallbackRecords) { + record.mExecutor.execute( + () -> + record.mRouteListingPreferenceCallback.onRouteListingPreferenceChanged( + preference)); + } + } + private void notifyTransfer(RoutingController oldController, RoutingController newController) { for (TransferCallbackRecord record : mTransferCallbackRecords) { record.mExecutor.execute( @@ -1091,6 +1153,12 @@ public final class MediaRouter2 { } } + private void notifyRequestFailed(int reason) { + for (TransferCallbackRecord record : mTransferCallbackRecords) { + record.mExecutor.execute(() -> record.mTransferCallback.onRequestFailed(reason)); + } + } + private void notifyStop(RoutingController controller) { for (TransferCallbackRecord record : mTransferCallbackRecords) { record.mExecutor.execute(() -> record.mTransferCallback.onStop(controller)); @@ -1155,6 +1223,12 @@ public final class MediaRouter2 { public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {} } + /** @hide */ + public abstract static class RouteListingPreferenceCallback { + /** @hide */ + public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {} + } + /** Callback for receiving events on media transfer. */ public abstract static class TransferCallback { /** @@ -1191,6 +1265,15 @@ public final class MediaRouter2 { * @param controller the controller that controlled the stopped media routing */ public void onStop(@NonNull RoutingController controller) {} + + /** + * Called when a routing request fails. + * + * @param reason Reason for failure as per {@link + * android.media.MediaRoute2ProviderService.Reason} + * @hide + */ + public void onRequestFailed(int reason) {} } /** @@ -1684,6 +1767,35 @@ public final class MediaRouter2 { } } + private static final class RouteListingPreferenceCallbackRecord { + public final Executor mExecutor; + public final RouteListingPreferenceCallback mRouteListingPreferenceCallback; + + /* package */ RouteListingPreferenceCallbackRecord( + @NonNull Executor executor, + @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) { + mExecutor = executor; + mRouteListingPreferenceCallback = routeListingPreferenceCallback; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RouteListingPreferenceCallbackRecord)) { + return false; + } + return mRouteListingPreferenceCallback + == ((RouteListingPreferenceCallbackRecord) obj).mRouteListingPreferenceCallback; + } + + @Override + public int hashCode() { + return mRouteListingPreferenceCallback.hashCode(); + } + } + static final class TransferCallbackRecord { public final Executor mExecutor; public final TransferCallback mTransferCallback; @@ -2549,6 +2661,24 @@ public final class MediaRouter2 { notifyPreferredFeaturesChanged(preference.getPreferredFeatures()); } + private void onRouteListingPreferenceChangedOnHandler( + @NonNull String packageName, + @Nullable RouteListingPreference routeListingPreference) { + if (!TextUtils.equals(getClientPackageName(), packageName)) { + return; + } + + synchronized (mLock) { + if (Objects.equals(mRouteListingPreference, routeListingPreference)) { + return; + } + + mRouteListingPreference = routeListingPreference; + } + + notifyRouteListingPreferenceUpdated(routeListingPreference); + } + private void onRoutesUpdatedOnHandler(@NonNull List<MediaRoute2Info> routes) { synchronized (mLock) { mRoutes.clear(); @@ -2572,7 +2702,7 @@ public final class MediaRouter2 { mTransferRequests.remove(matchingRequest); onTransferFailed(matchingRequest.mOldSessionInfo, matchingRequest.mTargetRoute); } else { - // Does nothing. + notifyRequestFailed(reason); } } @@ -2620,7 +2750,12 @@ public final class MediaRouter2 { @Override public void notifyRouteListingPreferenceChange( String packageName, RouteListingPreference routeListingPreference) { - // TODO(b/281067101): Add callback and getter for RouteListingPreference. + mHandler.sendMessage( + obtainMessage( + ProxyMediaRouter2Impl::onRouteListingPreferenceChangedOnHandler, + ProxyMediaRouter2Impl.this, + packageName, + routeListingPreference)); } @Override diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html index 67d218413e74..d2c1c04265bf 100644 --- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html +++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html @@ -20,7 +20,7 @@ <meta charset="UTF-8"> <meta name="description" content=" This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of - SlicePurchaseWebInterface. Test slice purchase application behavior using ADB shell commands and + DataBoostWebServiceFlow. Test slice purchase application behavior using ADB shell commands and the APIs below: FROM TERMINAL: @@ -65,8 +65,8 @@ <p id="requested_capability"></p> <h2>Notify purchase successful</h2> - <button type="button" onclick="testNotifyPurchaseSuccessful(60000)"> - Notify purchase successful for 1 minute + <button type="button" onclick="testNotifyPurchaseSuccessful()"> + Notify purchase successful </button> <p id="purchase_successful"></p> diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js index 02c4feac7ee4..84ab1f933838 100644 --- a/packages/CarrierDefaultApp/assets/slice_purchase_test.js +++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js @@ -15,19 +15,19 @@ */ function testGetRequestedCapability() { - let capability = SlicePurchaseWebInterface.getRequestedCapability(); + let capability = DataBoostWebServiceFlow.getRequestedCapability(); document.getElementById("requested_capability").innerHTML = "Premium capability requested: " + capability; } -function testNotifyPurchaseSuccessful(duration_ms_long = 0) { - SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long); +function testNotifyPurchaseSuccessful() { + DataBoostWebServiceFlow.notifyPurchaseSuccessful(); document.getElementById("purchase_successful").innerHTML = - "Notified purchase success for duration: " + duration_ms_long; + "Notified purchase successful."; } function testNotifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") { - SlicePurchaseWebInterface.notifyPurchaseFailed(failure_code, failure_reason); + DataBoostWebServiceFlow.notifyPurchaseFailed(failure_code, failure_reason); document.getElementById("purchase_failed").innerHTML = "Notified purchase failed."; } diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java index 8547898df678..0aadd31b6e18 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java @@ -24,13 +24,13 @@ import android.webkit.JavascriptInterface; import com.android.phone.slice.SlicePurchaseController; /** - * Slice purchase web interface class allowing carrier websites to send responses back to the + * Data boost web service flow interface allowing carrier websites to send responses back to the * slice purchase application using JavaScript. */ -public class SlicePurchaseWebInterface { +public class DataBoostWebServiceFlow { @NonNull SlicePurchaseActivity mActivity; - public SlicePurchaseWebInterface(@NonNull SlicePurchaseActivity activity) { + public DataBoostWebServiceFlow(@NonNull SlicePurchaseActivity activity) { mActivity = activity; } @@ -41,7 +41,7 @@ public class SlicePurchaseWebInterface { * This can be called using the JavaScript below: * <script type="text/javascript"> * function getRequestedCapability(duration) { - * SlicePurchaseWebInterface.getRequestedCapability(); + * DataBoostWebServiceFlow.getRequestedCapability(); * } * </script> */ @@ -57,16 +57,14 @@ public class SlicePurchaseWebInterface { * * This can be called using the JavaScript below: * <script type="text/javascript"> - * function notifyPurchaseSuccessful(duration_ms_long = 0) { - * SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long); + * function notifyPurchaseSuccessful() { + * DataBoostWebServiceFlow.notifyPurchaseSuccessful(); * } * </script> - * - * @param duration The duration for which the premium capability is purchased in milliseconds. */ @JavascriptInterface - public void notifyPurchaseSuccessful(long duration) { - mActivity.onPurchaseSuccessful(duration); + public void notifyPurchaseSuccessful() { + mActivity.onPurchaseSuccessful(); } /** @@ -76,7 +74,7 @@ public class SlicePurchaseWebInterface { * This can be called using the JavaScript below: * <script type="text/javascript"> * function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") { - * SlicePurchaseWebInterface.notifyPurchaseFailed(); + * DataBoostWebServiceFlow.notifyPurchaseFailed(); * } * </script> * diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java index 946185a3c420..d304394dacb7 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java @@ -32,21 +32,19 @@ import android.webkit.WebView; import com.android.phone.slice.SlicePurchaseController; import java.net.URL; -import java.util.concurrent.TimeUnit; /** * Activity that launches when the user clicks on the performance boost notification. * This will open a {@link WebView} for the carrier website to allow the user to complete the * premium capability purchase. * The carrier website can get the requested premium capability using the JavaScript interface - * method {@code SlicePurchaseWebInterface.getRequestedCapability()}. + * method {@code DataBoostWebServiceFlow.getRequestedCapability()}. * If the purchase is successful, the carrier website shall notify the slice purchase application * using the JavaScript interface method - * {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is - * the optional duration of the performance boost. + * {@code DataBoostWebServiceFlow.notifyPurchaseSuccessful()}. * If the purchase was not successful, the carrier website shall notify the slice purchase * application using the JavaScript interface method - * {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the + * {@code DataBoostWebServiceFlow.notifyPurchaseFailed(code, reason)}, where {@code code} is the * {@link SlicePurchaseController.FailureCode} indicating the reason for failure and {@code reason} * is the human-readable reason for failure if the failure code is * {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN}. @@ -118,15 +116,11 @@ public class SlicePurchaseActivity extends Activity { setupWebView(); } - protected void onPurchaseSuccessful(long duration) { + protected void onPurchaseSuccessful() { logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium " - + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability) - + (duration > 0 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes." - : ".")); - Intent intent = new Intent(); - intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration); - SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext, - mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent); + + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)); + SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse( + mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS); finishAndRemoveTask(); } @@ -178,7 +172,7 @@ public class SlicePurchaseActivity extends Activity { // the slice purchase application. mWebView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface( - new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface"); + new DataBoostWebServiceFlow(this), "DataBoostWebServiceFlow"); // Display WebView setContentView(mWebView); diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java index daf0b42dbd7c..e7a75e58105d 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java @@ -53,6 +53,7 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas private static final int PHONE_ID = 0; @Mock PendingIntent mPendingIntent; + @Mock PendingIntent mSuccessfulIntent; @Mock PendingIntent mCanceledIntent; @Mock CarrierConfigManager mCarrierConfigManager; @Mock NotificationManager mNotificationManager; @@ -107,20 +108,18 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas doReturn(true).when(mCanceledIntent).isBroadcast(); doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra( eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class)); + doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mSuccessfulIntent).getCreatorPackage(); + doReturn(true).when(mSuccessfulIntent).isBroadcast(); + doReturn(mSuccessfulIntent).when(spiedIntent).getParcelableExtra( + eq(SlicePurchaseController.EXTRA_INTENT_SUCCESS), eq(PendingIntent.class)); mSlicePurchaseActivity = startActivity(spiedIntent, null, null); } @Test public void testOnPurchaseSuccessful() throws Exception { - int duration = 5 * 60 * 1000; // 5 minutes - int invalidDuration = -1; - mSlicePurchaseActivity.onPurchaseSuccessful(duration); - ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture()); - Intent intent = intentCaptor.getValue(); - assertEquals(duration, intent.getLongExtra( - SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration)); + mSlicePurchaseActivity.onPurchaseSuccessful(); + verify(mSuccessfulIntent).send(); } @Test diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 0db88af92a56..0c1b793102bf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -51,6 +51,7 @@ public class CachedBluetoothDeviceManager { CsipDeviceManager mCsipDeviceManager; BluetoothDevice mOngoingSetMemberPair; boolean mIsLateBonding; + int mGroupIdOfLateBonding; public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) { mContext = context; @@ -213,6 +214,14 @@ public class CachedBluetoothDeviceManager { * @return The name, or if unavailable, the address. */ public String getName(BluetoothDevice device) { + if (isOngoingPairByCsip(device)) { + CachedBluetoothDevice firstDevice = + mCsipDeviceManager.getFirstMemberDevice(mGroupIdOfLateBonding); + if (firstDevice != null && firstDevice.getName() != null) { + return firstDevice.getName(); + } + } + CachedBluetoothDevice cachedDevice = findDevice(device); if (cachedDevice != null && cachedDevice.getName() != null) { return cachedDevice.getName(); @@ -314,6 +323,7 @@ public class CachedBluetoothDeviceManager { // To clear the SetMemberPair flag when the Bluetooth is turning off. mOngoingSetMemberPair = null; mIsLateBonding = false; + mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } } @@ -426,6 +436,7 @@ public class CachedBluetoothDeviceManager { return false; } + Log.d(TAG, "isLateBonding: " + mIsLateBonding); return mIsLateBonding; } @@ -444,11 +455,13 @@ public class CachedBluetoothDeviceManager { Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " groupId=" + groupId + " by CSIP "); mOngoingSetMemberPair = device; mIsLateBonding = checkLateBonding(groupId); + mGroupIdOfLateBonding = groupId; syncConfigFromMainDevice(device, groupId); if (!device.createBond(BluetoothDevice.TRANSPORT_LE)) { Log.d(TAG, "Bonding could not be started"); mOngoingSetMemberPair = null; mIsLateBonding = false; + mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } } @@ -494,6 +507,7 @@ public class CachedBluetoothDeviceManager { mOngoingSetMemberPair = null; mIsLateBonding = false; + mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; if (bondState != BluetoothDevice.BOND_NONE) { if (findDevice(device) == null) { final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index c4f09cecfa1f..f911d35757f6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -111,6 +111,9 @@ public class DreamBackend { public static final int COMPLICATION_TYPE_SMARTSPACE = 7; public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8; + private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1; + private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0; + private final Context mContext; private final IDreamManager mDreamManager; private final DreamInfoComparator mComparator; @@ -311,8 +314,14 @@ public class DreamBackend { /** Gets whether home controls button is enabled on the dream */ private boolean getHomeControlsEnabled() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1; + return Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, + LOCKSCREEN_SHOW_CONTROLS_DEFAULT) == 1 + && Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, + SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT) == 1; } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java index 22ec12d44d6d..2edf403e5c00 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; +import android.provider.Settings; import org.junit.After; import org.junit.Before; @@ -84,6 +85,7 @@ public final class DreamBackendTest { @Test public void testComplicationsEnabledByDefault() { + setControlsEnabledOnLockscreen(true); assertThat(mBackend.getComplicationsEnabled()).isTrue(); assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( SUPPORTED_DREAM_COMPLICATIONS_LIST); @@ -91,6 +93,7 @@ public final class DreamBackendTest { @Test public void testEnableComplicationExplicitly() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(true); assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( SUPPORTED_DREAM_COMPLICATIONS_LIST); @@ -99,6 +102,7 @@ public final class DreamBackendTest { @Test public void testDisableComplications() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(false); assertThat(mBackend.getEnabledComplications()) .containsExactly(COMPLICATION_TYPE_HOME_CONTROLS); @@ -107,6 +111,7 @@ public final class DreamBackendTest { @Test public void testHomeControlsDisabled_ComplicationsEnabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(true); mBackend.setHomeControlsEnabled(false); // Home controls should not be enabled, only date and time. @@ -118,6 +123,7 @@ public final class DreamBackendTest { @Test public void testHomeControlsDisabled_ComplicationsDisabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(false); mBackend.setHomeControlsEnabled(false); assertThat(mBackend.getEnabledComplications()).isEmpty(); @@ -125,9 +131,9 @@ public final class DreamBackendTest { @Test public void testHomeControlsEnabled_ComplicationsDisabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(false); mBackend.setHomeControlsEnabled(true); - // Home controls should not be enabled, only date and time. final List<Integer> enabledComplications = Collections.singletonList(COMPLICATION_TYPE_HOME_CONTROLS); assertThat(mBackend.getEnabledComplications()) @@ -136,9 +142,9 @@ public final class DreamBackendTest { @Test public void testHomeControlsEnabled_ComplicationsEnabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(true); mBackend.setHomeControlsEnabled(true); - // Home controls should not be enabled, only date and time. final List<Integer> enabledComplications = Arrays.asList( COMPLICATION_TYPE_HOME_CONTROLS, @@ -148,4 +154,26 @@ public final class DreamBackendTest { assertThat(mBackend.getEnabledComplications()) .containsExactlyElementsIn(enabledComplications); } + + @Test + public void testHomeControlsEnabled_lockscreenDisabled() { + setControlsEnabledOnLockscreen(false); + mBackend.setComplicationsEnabled(true); + mBackend.setHomeControlsEnabled(true); + // Home controls should not be enabled, only date and time. + final List<Integer> enabledComplications = + Arrays.asList( + COMPLICATION_TYPE_DATE, + COMPLICATION_TYPE_TIME + ); + assertThat(mBackend.getEnabledComplications()) + .containsExactlyElementsIn(enabledComplications); + } + + private void setControlsEnabledOnLockscreen(boolean enabled) { + Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, + enabled ? 1 : 0); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 5544b09e5d0b..1192e0086123 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -785,12 +785,6 @@ class SettingsProtoDumpUtil { Settings.Global.ANGLE_EGL_FEATURES, GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES); dumpSetting(s, p, - Settings.Global.ANGLE_DEFERLIST, - GlobalSettingsProto.Gpu.ANGLE_DEFERLIST); - dumpSetting(s, p, - Settings.Global.ANGLE_DEFERLIST_MODE, - GlobalSettingsProto.Gpu.ANGLE_DEFERLIST_MODE); - dumpSetting(s, p, Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, GlobalSettingsProto.Gpu.SHOW_ANGLE_IN_USE_DIALOG); dumpSetting(s, p, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c2ab87ab0e78..a64cf11d3bf7 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -519,8 +519,6 @@ public class SettingsBackupTest { Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES, Settings.Global.ANGLE_EGL_FEATURES, - Settings.Global.ANGLE_DEFERLIST, - Settings.Global.ANGLE_DEFERLIST_MODE, Settings.Global.UPDATABLE_DRIVER_ALL_APPS, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt index d1ee18adaf6e..25269dc0d7d8 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt @@ -85,7 +85,7 @@ private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp) @Composable private fun filledButtonColors(): ButtonColors { - val colors = LocalAndroidColorScheme.current + val colors = LocalAndroidColorScheme.current.deprecated return ButtonDefaults.buttonColors( containerColor = colors.colorAccentPrimary, contentColor = colors.textColorOnAccent, @@ -94,7 +94,7 @@ private fun filledButtonColors(): ButtonColors { @Composable private fun outlineButtonColors(): ButtonColors { - val colors = LocalAndroidColorScheme.current + val colors = LocalAndroidColorScheme.current.deprecated return ButtonDefaults.outlinedButtonColors( contentColor = colors.textColorPrimary, ) @@ -102,7 +102,7 @@ private fun outlineButtonColors(): ButtonColors { @Composable private fun outlineButtonBorder(): BorderStroke { - val colors = LocalAndroidColorScheme.current + val colors = LocalAndroidColorScheme.current.deprecated return BorderStroke( width = 1.dp, color = colors.colorAccentPrimaryVariant, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt index 4f1657faddee..1d6f813cfbdf 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt @@ -37,33 +37,83 @@ val LocalAndroidColorScheme = * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future, * most of the colors in this class will be removed in favor of their M3 counterpart. */ -class AndroidColorScheme internal constructor(context: Context) { - val colorPrimary = getColor(context, R.attr.colorPrimary) - val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark) - val colorAccent = getColor(context, R.attr.colorAccent) - val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary) - val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary) - val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary) - val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant) - val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant) - val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant) - val colorSurface = getColor(context, R.attr.colorSurface) - val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight) - val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant) - val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader) - val colorError = getColor(context, R.attr.colorError) - val colorBackground = getColor(context, R.attr.colorBackground) - val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating) - val panelColorBackground = getColor(context, R.attr.panelColorBackground) - val textColorPrimary = getColor(context, R.attr.textColorPrimary) - val textColorSecondary = getColor(context, R.attr.textColorSecondary) - val textColorTertiary = getColor(context, R.attr.textColorTertiary) - val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse) - val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse) - val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse) - val textColorOnAccent = getColor(context, R.attr.textColorOnAccent) - val colorForeground = getColor(context, R.attr.colorForeground) - val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse) +class AndroidColorScheme(context: Context) { + val onSecondaryFixedVariant = getColor(context, R.attr.materialColorOnSecondaryFixedVariant) + val onTertiaryFixedVariant = getColor(context, R.attr.materialColorOnTertiaryFixedVariant) + val surfaceContainerLowest = getColor(context, R.attr.materialColorSurfaceContainerLowest) + val onPrimaryFixedVariant = getColor(context, R.attr.materialColorOnPrimaryFixedVariant) + val onSecondaryContainer = getColor(context, R.attr.materialColorOnSecondaryContainer) + val onTertiaryContainer = getColor(context, R.attr.materialColorOnTertiaryContainer) + val surfaceContainerLow = getColor(context, R.attr.materialColorSurfaceContainerLow) + val onPrimaryContainer = getColor(context, R.attr.materialColorOnPrimaryContainer) + val secondaryFixedDim = getColor(context, R.attr.materialColorSecondaryFixedDim) + val onErrorContainer = getColor(context, R.attr.materialColorOnErrorContainer) + val onSecondaryFixed = getColor(context, R.attr.materialColorOnSecondaryFixed) + val onSurfaceInverse = getColor(context, R.attr.materialColorOnSurfaceInverse) + val tertiaryFixedDim = getColor(context, R.attr.materialColorTertiaryFixedDim) + val onTertiaryFixed = getColor(context, R.attr.materialColorOnTertiaryFixed) + val primaryFixedDim = getColor(context, R.attr.materialColorPrimaryFixedDim) + val secondaryContainer = getColor(context, R.attr.materialColorSecondaryContainer) + val errorContainer = getColor(context, R.attr.materialColorErrorContainer) + val onPrimaryFixed = getColor(context, R.attr.materialColorOnPrimaryFixed) + val primaryInverse = getColor(context, R.attr.materialColorPrimaryInverse) + val secondaryFixed = getColor(context, R.attr.materialColorSecondaryFixed) + val surfaceInverse = getColor(context, R.attr.materialColorSurfaceInverse) + val surfaceVariant = getColor(context, R.attr.materialColorSurfaceVariant) + val tertiaryContainer = getColor(context, R.attr.materialColorTertiaryContainer) + val tertiaryFixed = getColor(context, R.attr.materialColorTertiaryFixed) + val primaryContainer = getColor(context, R.attr.materialColorPrimaryContainer) + val onBackground = getColor(context, R.attr.materialColorOnBackground) + val primaryFixed = getColor(context, R.attr.materialColorPrimaryFixed) + val onSecondary = getColor(context, R.attr.materialColorOnSecondary) + val onTertiary = getColor(context, R.attr.materialColorOnTertiary) + val surfaceDim = getColor(context, R.attr.materialColorSurfaceDim) + val surfaceBright = getColor(context, R.attr.materialColorSurfaceBright) + val onError = getColor(context, R.attr.materialColorOnError) + val surface = getColor(context, R.attr.materialColorSurface) + val surfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh) + val surfaceContainerHighest = getColor(context, R.attr.materialColorSurfaceContainerHighest) + val onSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant) + val outline = getColor(context, R.attr.materialColorOutline) + val outlineVariant = getColor(context, R.attr.materialColorOutlineVariant) + val onPrimary = getColor(context, R.attr.materialColorOnPrimary) + val onSurface = getColor(context, R.attr.materialColorOnSurface) + val surfaceContainer = getColor(context, R.attr.materialColorSurfaceContainer) + val primary = getColor(context, R.attr.materialColorPrimary) + val secondary = getColor(context, R.attr.materialColorSecondary) + val tertiary = getColor(context, R.attr.materialColorTertiary) + + @Deprecated("Use the new android tokens: go/sysui-colors") + val deprecated = DeprecatedValues(context) + + class DeprecatedValues(context: Context) { + val colorPrimary = getColor(context, R.attr.colorPrimary) + val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark) + val colorAccent = getColor(context, R.attr.colorAccent) + val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary) + val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary) + val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary) + val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant) + val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant) + val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant) + val colorSurface = getColor(context, R.attr.colorSurface) + val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight) + val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant) + val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader) + val colorError = getColor(context, R.attr.colorError) + val colorBackground = getColor(context, R.attr.colorBackground) + val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating) + val panelColorBackground = getColor(context, R.attr.panelColorBackground) + val textColorPrimary = getColor(context, R.attr.textColorPrimary) + val textColorSecondary = getColor(context, R.attr.textColorSecondary) + val textColorTertiary = getColor(context, R.attr.textColorTertiary) + val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse) + val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse) + val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse) + val textColorOnAccent = getColor(context, R.attr.textColorOnAccent) + val colorForeground = getColor(context, R.attr.colorForeground) + val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse) + } companion object { fun getColor(context: Context, attr: Int): Color { diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt index 9bc68564de90..fe340174d56b 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt @@ -40,7 +40,9 @@ class SystemUIThemeTest { @Test fun testAndroidColorsAreAvailableInsideTheme() { composeRule.setContent { - PlatformTheme { Text("foo", color = LocalAndroidColorScheme.current.colorAccent) } + PlatformTheme { + Text("foo", color = LocalAndroidColorScheme.current.deprecated.colorAccent) + } } composeRule.onNodeWithText("foo").assertIsDisplayed() @@ -50,7 +52,7 @@ class SystemUIThemeTest { fun testAccessingAndroidColorsWithoutThemeThrows() { assertThrows(IllegalStateException::class.java) { composeRule.setContent { - Text("foo", color = LocalAndroidColorScheme.current.colorAccent) + Text("foo", color = LocalAndroidColorScheme.current.deprecated.colorAccent) } } } diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index 24064b1261b7..5413f9097c5b 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -17,10 +17,12 @@ package com.android.systemui.scene.ui.composable import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneContainerNames import dagger.Module import dagger.multibindings.Multibinds +import javax.inject.Named @Module interface SceneModule { - @Multibinds fun scenes(): Set<Scene> + @Multibinds @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun scenes(): Set<Scene> } diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index ee53ece5c944..954bad56bcc2 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -17,22 +17,32 @@ package com.android.systemui.scene.ui.composable import com.android.systemui.bouncer.ui.composable.BouncerScene +import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.composable.LockscreenScene +import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.qs.ui.composable.QuickSettingsScene +import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.shade.ui.composable.ShadeScene +import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel import dagger.Module import dagger.Provides +import javax.inject.Named +import kotlinx.coroutines.CoroutineScope @Module object SceneModule { @Provides + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun scenes( - bouncer: BouncerScene, - gone: GoneScene, - lockScreen: LockscreenScene, - qs: QuickSettingsScene, - shade: ShadeScene, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) bouncer: BouncerScene, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) gone: GoneScene, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) lockScreen: LockscreenScene, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) qs: QuickSettingsScene, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) shade: ShadeScene, ): Set<Scene> { return setOf( bouncer, @@ -42,4 +52,71 @@ object SceneModule { shade, ) } + + @Provides + @SysUISingleton + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + fun bouncerScene( + viewModelFactory: BouncerViewModel.Factory, + ): BouncerScene { + return BouncerScene( + viewModel = + viewModelFactory.create( + containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, + ), + ) + } + + @Provides + @SysUISingleton + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + fun goneScene(): GoneScene { + return GoneScene() + } + + @Provides + @SysUISingleton + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + fun lockscreenScene( + @Application applicationScope: CoroutineScope, + viewModelFactory: LockscreenSceneViewModel.Factory, + ): LockscreenScene { + return LockscreenScene( + applicationScope = applicationScope, + viewModel = + viewModelFactory.create( + containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, + ), + ) + } + + @Provides + @SysUISingleton + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + fun quickSettingsScene( + viewModelFactory: QuickSettingsSceneViewModel.Factory, + ): QuickSettingsScene { + return QuickSettingsScene( + viewModel = + viewModelFactory.create( + containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, + ), + ) + } + + @Provides + @SysUISingleton + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + fun shadeScene( + @Application applicationScope: CoroutineScope, + viewModelFactory: ShadeSceneViewModel.Factory, + ): ShadeScene { + return ShadeScene( + applicationScope = applicationScope, + viewModel = + viewModelFactory.create( + containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, + ), + ) + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index f48bab90ba42..3c74ef5adfeb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -40,22 +40,17 @@ import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */ -@SysUISingleton -class BouncerScene -@Inject -constructor( - private val viewModelFactory: BouncerViewModel.Factory, +class BouncerScene( + private val viewModel: BouncerViewModel, ) : ComposableScene { override val key = SceneKey.Bouncer @@ -73,7 +68,7 @@ constructor( override fun Content( containerName: String, modifier: Modifier, - ) = BouncerScene(viewModelFactory.create(containerName), modifier) + ) = BouncerScene(viewModel, modifier) } @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index 7c07a8b3d054..10652678739c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.scene.shared.model.Direction @@ -39,7 +38,6 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -47,29 +45,22 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** The lock screen scene shows when the device is locked. */ -@SysUISingleton -class LockscreenScene -@Inject -constructor( +class LockscreenScene( @Application private val applicationScope: CoroutineScope, - private val viewModelFactory: LockscreenSceneViewModel.Factory, + private val viewModel: LockscreenSceneViewModel, ) : ComposableScene { override val key = SceneKey.Lockscreen - private var unsafeViewModel: LockscreenSceneViewModel? = null - override fun destinationScenes( containerName: String, ): StateFlow<Map<UserAction, SceneModel>> = - getOrCreateViewModelSingleton(containerName).let { viewModel -> - viewModel.upDestinationSceneKey - .map { pageKey -> destinationScenes(up = pageKey) } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value) - ) - } + viewModel.upDestinationSceneKey + .map { pageKey -> destinationScenes(up = pageKey) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value) + ) @Composable override fun Content( @@ -77,7 +68,7 @@ constructor( modifier: Modifier, ) { LockscreenScene( - viewModel = getOrCreateViewModelSingleton(containerName), + viewModel = viewModel, modifier = modifier, ) } @@ -90,13 +81,6 @@ constructor( UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade) ) } - - private fun getOrCreateViewModelSingleton( - containerName: String, - ): LockscreenSceneViewModel { - return unsafeViewModel - ?: viewModelFactory.create(containerName).also { unsafeViewModel = it } - } } @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index a74e56b6e2f2..f88fc21addff 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -91,7 +91,7 @@ fun PeopleScreen( // Make sure to use the Android colors and not the default Material3 colors to have the exact // same colors as the View implementation. - val androidColors = LocalAndroidColorScheme.current + val androidColors = LocalAndroidColorScheme.current.deprecated Surface( color = androidColors.colorBackground, contentColor = androidColors.textColorPrimary, @@ -170,7 +170,7 @@ private fun LazyListScope.ConversationList( stringResource(headerTextResource), Modifier.padding(start = 16.dp), style = MaterialTheme.typography.labelLarge, - color = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, + color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant, ) Spacer(Modifier.height(10.dp)) @@ -180,7 +180,7 @@ private fun LazyListScope.ConversationList( if (index > 0) { item { Divider( - color = LocalAndroidColorScheme.current.colorBackground, + color = LocalAndroidColorScheme.current.deprecated.colorBackground, thickness = 2.dp, ) } @@ -204,7 +204,7 @@ private fun Tile( withTopCornerRadius: Boolean, withBottomCornerRadius: Boolean, ) { - val androidColors = LocalAndroidColorScheme.current + val androidColors = LocalAndroidColorScheme.current.deprecated val cornerRadius = dimensionResource(R.dimen.people_space_widget_radius) val topCornerRadius = if (withTopCornerRadius) cornerRadius else 0.dp val bottomCornerRadius = if (withBottomCornerRadius) cornerRadius else 0.dp diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt index 0484ff475cdf..1e6f4a2f5b23 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt @@ -76,8 +76,8 @@ internal fun PeopleScreenEmpty( Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp), colors = ButtonDefaults.buttonColors( - containerColor = androidColors.colorAccentPrimary, - contentColor = androidColors.textColorOnAccent, + containerColor = androidColors.deprecated.colorAccentPrimary, + contentColor = androidColors.deprecated.textColorOnAccent, ) ) { Text(stringResource(R.string.got_it)) @@ -90,8 +90,8 @@ private fun ExampleTile() { val androidColors = LocalAndroidColorScheme.current Surface( shape = RoundedCornerShape(28.dp), - color = androidColors.colorSurface, - contentColor = androidColors.textColorPrimary, + color = androidColors.deprecated.colorSurface, + contentColor = androidColors.deprecated.textColorPrimary, ) { Row( Modifier.padding(vertical = 20.dp, horizontal = 16.dp), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 349f5c333116..75bf2813a321 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -122,7 +122,7 @@ fun FooterActions( } val backgroundColor = colorAttr(R.attr.underSurfaceColor) - val contentColor = LocalAndroidColorScheme.current.textColorPrimary + val contentColor = LocalAndroidColorScheme.current.deprecated.textColorPrimary val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius) val backgroundModifier = remember( @@ -287,7 +287,7 @@ private fun NumberButton( number.toString(), modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyLarge, - color = LocalAndroidColorScheme.current.textColorPrimary, + color = LocalAndroidColorScheme.current.deprecated.textColorPrimary, // TODO(b/242040009): This should only use a standard text style instead and // should not override the text size. fontSize = 18.sp, @@ -305,7 +305,7 @@ private fun NumberButton( @Composable private fun NewChangesDot(modifier: Modifier = Modifier) { val contentDescription = stringResource(R.string.fgs_dot_content_description) - val color = LocalAndroidColorScheme.current.colorAccentTertiary + val color = LocalAndroidColorScheme.current.deprecated.colorAccentTertiary Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) { drawCircle(color) @@ -324,8 +324,9 @@ private fun TextButton( Expandable( shape = CircleShape, color = colorAttr(R.attr.underSurfaceColor), - contentColor = LocalAndroidColorScheme.current.textColorSecondary, - borderStroke = BorderStroke(1.dp, LocalAndroidColorScheme.current.colorBackground), + contentColor = LocalAndroidColorScheme.current.deprecated.textColorSecondary, + borderStroke = + BorderStroke(1.dp, LocalAndroidColorScheme.current.deprecated.colorBackground), modifier = modifier.padding(horizontal = 4.dp), onClick = onClick, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 58db37eca8d5..30b80ca7fc1e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -27,24 +27,19 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */ -@SysUISingleton -class QuickSettingsScene -@Inject -constructor( - private val viewModelFactory: QuickSettingsSceneViewModel.Factory, +class QuickSettingsScene( + private val viewModel: QuickSettingsSceneViewModel, ) : ComposableScene { override val key = SceneKey.QuickSettings @@ -64,7 +59,7 @@ constructor( modifier: Modifier, ) { QuickSettingsScene( - viewModel = viewModelFactory.create(containerName), + viewModel = viewModel, modifier = modifier, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index b387463d3d90..0a4da1d6ba1e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -23,12 +23,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -37,8 +35,7 @@ import kotlinx.coroutines.flow.asStateFlow * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any * content from the scene framework. */ -@SysUISingleton -class GoneScene @Inject constructor() : ComposableScene { +class GoneScene : ComposableScene { override val key = SceneKey.Gone override fun destinationScenes( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index e4513d01ea37..20e175160aa6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -35,7 +34,6 @@ import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -43,29 +41,22 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */ -@SysUISingleton -class ShadeScene -@Inject -constructor( +class ShadeScene( @Application private val applicationScope: CoroutineScope, - private val viewModelFactory: ShadeSceneViewModel.Factory, + private val viewModel: ShadeSceneViewModel, ) : ComposableScene { override val key = SceneKey.Shade - private var unsafeViewModel: ShadeSceneViewModel? = null - override fun destinationScenes( containerName: String, ): StateFlow<Map<UserAction, SceneModel>> = - getOrCreateViewModelSingleton(containerName).let { viewModel -> - viewModel.upDestinationSceneKey - .map { sceneKey -> destinationScenes(up = sceneKey) } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value), - ) - } + viewModel.upDestinationSceneKey + .map { sceneKey -> destinationScenes(up = sceneKey) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value), + ) @Composable override fun Content( @@ -73,7 +64,7 @@ constructor( modifier: Modifier, ) { ShadeScene( - viewModel = getOrCreateViewModelSingleton(containerName), + viewModel = viewModel, modifier = modifier, ) } @@ -86,13 +77,6 @@ constructor( UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.QuickSettings), ) } - - private fun getOrCreateViewModelSingleton( - containerName: String, - ): ShadeSceneViewModel { - return unsafeViewModel - ?: viewModelFactory.create(containerName).also { unsafeViewModel = it } - } } @Composable diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 8dd2c39e7a58..465b73e6de19 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -272,6 +272,7 @@ class AnimatableClockView @JvmOverloads constructor( color = lockScreenColor, animate = isAnimationEnabled, duration = APPEAR_ANIM_DURATION, + interpolator = Interpolators.EMPHASIZED_DECELERATE, delay = 0, onAnimationEnd = null ) @@ -562,7 +563,7 @@ class AnimatableClockView @JvmOverloads constructor( private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm" private const val DOZE_ANIM_DURATION: Long = 300 - private const val APPEAR_ANIM_DURATION: Long = 350 + private const val APPEAR_ANIM_DURATION: Long = 833 private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500 private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 private const val COLOR_ANIM_DURATION: Long = 400 diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index cad2c162a589..4b7968953420 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -96,6 +96,7 @@ <!-- additional offset for clock switch area items --> <dimen name="small_clock_height">114dp</dimen> + <dimen name="small_clock_padding_top">28dp</dimen> <dimen name="clock_padding_start">28dp</dimen> <dimen name="below_clock_padding_start">32dp</dimen> <dimen name="below_clock_padding_end">16dp</dimen> diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml index b3b40f340ecb..81691898dfe5 100644 --- a/packages/SystemUI/res/layout/auth_biometric_contents.xml +++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - +<!-- TODO(b/251476085): inline in biometric_prompt_layout after Biometric*Views are un-flagged --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <TextView diff --git a/packages/SystemUI/res/layout/auth_biometric_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_face_view.xml index be30f21af536..e3d073276369 100644 --- a/packages/SystemUI/res/layout/auth_biometric_face_view.xml +++ b/packages/SystemUI/res/layout/auth_biometric_face_view.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - +<!-- TODO(b/251476085): remove after BiometricFaceView is un-flagged --> <com.android.systemui.biometrics.AuthBiometricFaceView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/contents" diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml index 05ca2a786e3a..896d8362f5b9 100644 --- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml +++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - +<!-- TODO(b/251476085): remove after BiometricFingerprintAndFaceView is un-flagged --> <com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/contents" diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml index 01ea31f8bdd2..e36f9796be47 100644 --- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml +++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - +<!-- TODO(b/251476085): remove after BiometricFingerprintView is un-flagged --> <com.android.systemui.biometrics.AuthBiometricFingerprintView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/contents" diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml new file mode 100644 index 000000000000..05ff1b1c2e6f --- /dev/null +++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml @@ -0,0 +1,176 @@ +<!-- + ~ 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. + --> +<com.android.systemui.biometrics.ui.BiometricPromptLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/contents" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="@integer/biometric_dialog_text_gravity" + android:singleLine="true" + android:marqueeRepeatLimit="1" + android:ellipsize="marquee" + android:importantForAccessibility="no" + style="@style/TextAppearance.AuthCredential.Title"/> + + <TextView + android:id="@+id/subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="@integer/biometric_dialog_text_gravity" + android:singleLine="true" + android:marqueeRepeatLimit="1" + android:ellipsize="marquee" + style="@style/TextAppearance.AuthCredential.Subtitle"/> + + <TextView + android:id="@+id/description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbars ="vertical" + android:importantForAccessibility="no" + style="@style/TextAppearance.AuthCredential.Description"/> + + <Space android:id="@+id/space_above_icon" + android:layout_width="match_parent" + android:layout_height="48dp" /> + + <FrameLayout + android:id="@+id/biometric_icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"> + + <com.airbnb.lottie.LottieAnimationView + android:id="@+id/biometric_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:contentDescription="@null" + android:scaleType="fitXY" /> + + <com.airbnb.lottie.LottieAnimationView + android:id="@+id/biometric_icon_overlay" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:contentDescription="@null" + android:scaleType="fitXY" /> + </FrameLayout> + + <!-- For sensors such as UDFPS, this view is used during custom measurement/layout to add extra + padding so that the biometric icon is always in the right physical position. --> + <Space android:id="@+id/space_below_icon" + android:layout_width="match_parent" + android:layout_height="12dp" /> + + <TextView + android:id="@+id/indicator" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="24dp" + android:textSize="12sp" + android:gravity="center_horizontal" + android:accessibilityLiveRegion="polite" + android:singleLine="true" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:scrollHorizontally="true" + android:fadingEdge="horizontal" + android:textColor="@color/biometric_dialog_gray"/> + + <LinearLayout + android:id="@+id/button_bar" + android:layout_width="match_parent" + android:layout_height="88dp" + style="?android:attr/buttonBarStyle" + android:orientation="horizontal" + android:paddingTop="24dp"> + + <Space android:id="@+id/leftSpacer" + android:layout_width="8dp" + android:layout_height="match_parent" + android:visibility="visible" /> + + <!-- Negative Button, reserved for app --> + <Button android:id="@+id/button_negative" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_gravity="center_vertical" + android:ellipsize="end" + android:maxLines="2" + android:maxWidth="@dimen/biometric_dialog_button_negative_max_width" + android:visibility="gone"/> + <!-- Cancel Button, replaces negative button when biometric is accepted --> + <Button android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_gravity="center_vertical" + android:maxWidth="@dimen/biometric_dialog_button_negative_max_width" + android:text="@string/cancel" + android:visibility="gone"/> + <!-- "Use Credential" Button, replaces if device credential is allowed --> + <Button android:id="@+id/button_use_credential" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_gravity="center_vertical" + android:maxWidth="@dimen/biometric_dialog_button_negative_max_width" + android:visibility="gone"/> + + <Space android:id="@+id/middleSpacer" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:visibility="visible"/> + + <!-- Positive Button --> + <Button android:id="@+id/button_confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:layout_gravity="center_vertical" + android:ellipsize="end" + android:maxLines="2" + android:maxWidth="@dimen/biometric_dialog_button_positive_max_width" + android:text="@string/biometric_dialog_confirm" + android:visibility="gone"/> + <!-- Try Again Button --> + <Button android:id="@+id/button_try_again" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:layout_gravity="center_vertical" + android:ellipsize="end" + android:maxLines="2" + android:maxWidth="@dimen/biometric_dialog_button_positive_max_width" + android:text="@string/biometric_dialog_try_again" + android:visibility="gone"/> + + <Space android:id="@+id/rightSpacer" + android:layout_width="8dp" + android:layout_height="match_parent" + android:visibility="visible" /> + </LinearLayout> + +</com.android.systemui.biometrics.ui.BiometricPromptLayout> diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml index ae052502a110..bbf3adfb8c67 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -73,7 +73,7 @@ android:tint="?android:attr/textColorSecondary" android:layout_gravity="center" android:layout_weight="0" - android:layout_marginRight="@dimen/screenrecord_option_padding"/> + android:layout_marginEnd="@dimen/screenrecord_option_padding"/> <Spinner android:id="@+id/screen_recording_options" android:layout_width="0dp" @@ -106,7 +106,7 @@ android:src="@drawable/ic_touch" android:tint="?android:attr/textColorSecondary" android:layout_gravity="center" - android:layout_marginRight="@dimen/screenrecord_option_padding"/> + android:layout_marginEnd="@dimen/screenrecord_option_padding"/> <TextView android:layout_width="0dp" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index 1112bcdbd14d..9b1fa23081b4 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -85,7 +85,7 @@ android:layout_height="@dimen/volume_ringer_drawer_icon_size" android:layout_gravity="center" android:tint="?android:attr/textColorPrimary" - android:src="@drawable/ic_volume_ringer_mute" /> + android:src="@drawable/ic_speaker_mute" /> </FrameLayout> @@ -102,7 +102,7 @@ android:layout_height="@dimen/volume_ringer_drawer_icon_size" android:layout_gravity="center" android:tint="?android:attr/textColorPrimary" - android:src="@drawable/ic_volume_ringer" /> + android:src="@drawable/ic_speaker_on" /> </FrameLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a62dead7834b..8d3ba364da06 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -716,7 +716,7 @@ <!-- Minimum margin between clock and status bar --> <dimen name="keyguard_clock_top_margin">18dp</dimen> <!-- The amount to shift the clocks during a small/large transition --> - <dimen name="keyguard_clock_switch_y_shift">10dp</dimen> + <dimen name="keyguard_clock_switch_y_shift">14dp</dimen> <!-- When large clock is showing, offset the smartspace by this amount --> <dimen name="keyguard_smartspace_top_offset">12dp</dimen> <!-- With the large clock, move up slightly from the center --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 16c86482b9c7..e8b9f2a35659 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -353,7 +353,7 @@ <!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]--> <string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string> <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]--> - <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face. Press the unlock icon to continue.</string> + <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face.</string> <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]--> <string name="biometric_dialog_tap_confirm_with_face_alt_1">Unlocked by face. Press to continue.</string> <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]--> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 7cce75fa71c6..a90a34c560cf 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -1,5 +1,8 @@ package com.android.keyguard; +import static android.view.View.ALPHA; +import static android.view.View.TRANSLATION_Y; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -35,11 +38,12 @@ public class KeyguardClockSwitch extends RelativeLayout { private static final String TAG = "KeyguardClockSwitch"; - private static final long CLOCK_OUT_MILLIS = 150; - private static final long CLOCK_IN_MILLIS = 200; - public static final long CLOCK_IN_START_DELAY_MILLIS = CLOCK_OUT_MILLIS / 2; - private static final long STATUS_AREA_START_DELAY_MILLIS = 50; - private static final long STATUS_AREA_MOVE_MILLIS = 350; + private static final long CLOCK_OUT_MILLIS = 133; + private static final long CLOCK_IN_MILLIS = 167; + public static final long CLOCK_IN_START_DELAY_MILLIS = 133; + private static final long STATUS_AREA_START_DELAY_MILLIS = 0; + private static final long STATUS_AREA_MOVE_UP_MILLIS = 967; + private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467; @IntDef({LARGE, SMALL}) @Retention(RetentionPolicy.SOURCE) @@ -66,6 +70,17 @@ public class KeyguardClockSwitch extends RelativeLayout { top + targetHeight); } + /** Returns a region for the small clock to position itself, based on the given parent. */ + public static Rect getSmallClockRegion(ViewGroup parent) { + int targetHeight = parent.getResources() + .getDimensionPixelSize(R.dimen.small_clock_text_size); + return new Rect( + parent.getLeft(), + parent.getTop(), + parent.getRight(), + parent.getTop() + targetHeight); + } + /** * Frame for small/large clocks */ @@ -90,7 +105,7 @@ public class KeyguardClockSwitch extends RelativeLayout { @VisibleForTesting AnimatorSet mClockInAnim = null; @VisibleForTesting AnimatorSet mClockOutAnim = null; - private ObjectAnimator mStatusAreaAnim = null; + private AnimatorSet mStatusAreaAnim = null; private int mClockSwitchYAmount; @VisibleForTesting boolean mChildrenAreLaidOut = false; @@ -172,13 +187,8 @@ public class KeyguardClockSwitch extends RelativeLayout { void updateClockTargetRegions() { if (mClock != null) { if (mSmallClockFrame.isLaidOut()) { - int targetHeight = getResources() - .getDimensionPixelSize(R.dimen.small_clock_text_size); - mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect( - mSmallClockFrame.getLeft(), - mSmallClockFrame.getTop(), - mSmallClockFrame.getRight(), - mSmallClockFrame.getTop() + targetHeight)); + Rect targetRegion = getSmallClockRegion(mSmallClockFrame); + mClock.getSmallClock().getEvents().onTargetRegionChanged(targetRegion); } if (mLargeClockFrame.isLaidOut()) { @@ -220,28 +230,34 @@ public class KeyguardClockSwitch extends RelativeLayout { mStatusAreaAnim = null; View in, out; - int direction = 1; - float statusAreaYTranslation; + float statusAreaYTranslation, clockInYTranslation, clockOutYTranslation; if (useLargeClock) { out = mSmallClockFrame; in = mLargeClockFrame; if (indexOfChild(in) == -1) addView(in, 0); - direction = -1; statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop() + mSmartspaceTopOffset; + clockInYTranslation = 0; + clockOutYTranslation = 0; // Small clock translation is handled with statusArea } else { in = mSmallClockFrame; out = mLargeClockFrame; statusAreaYTranslation = 0f; + clockInYTranslation = 0f; + clockOutYTranslation = mClockSwitchYAmount * -1f; - // Must remove in order for notifications to appear in the proper place + // Must remove in order for notifications to appear in the proper place, ideally this + // would happen after the out animation runs, but we can't guarantee that the + // nofications won't enter only after the out animation runs. removeView(out); } if (!animate) { out.setAlpha(0f); + out.setTranslationY(clockOutYTranslation); out.setVisibility(INVISIBLE); in.setAlpha(1f); + in.setTranslationY(clockInYTranslation); in.setVisibility(VISIBLE); mStatusArea.setTranslationY(statusAreaYTranslation); return; @@ -249,11 +265,10 @@ public class KeyguardClockSwitch extends RelativeLayout { mClockOutAnim = new AnimatorSet(); mClockOutAnim.setDuration(CLOCK_OUT_MILLIS); - mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + mClockOutAnim.setInterpolator(Interpolators.LINEAR); mClockOutAnim.playTogether( - ObjectAnimator.ofFloat(out, View.ALPHA, 0f), - ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0, - direction * -mClockSwitchYAmount)); + ObjectAnimator.ofFloat(out, ALPHA, 0f), + ObjectAnimator.ofFloat(out, TRANSLATION_Y, clockOutYTranslation)); mClockOutAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { if (mClockOutAnim == animation) { @@ -268,8 +283,9 @@ public class KeyguardClockSwitch extends RelativeLayout { mClockInAnim = new AnimatorSet(); mClockInAnim.setDuration(CLOCK_IN_MILLIS); mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f), - ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0)); + mClockInAnim.playTogether( + ObjectAnimator.ofFloat(in, ALPHA, 1f), + ObjectAnimator.ofFloat(in, TRANSLATION_Y, clockInYTranslation)); mClockInAnim.setStartDelay(CLOCK_IN_START_DELAY_MILLIS); mClockInAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { @@ -279,14 +295,14 @@ public class KeyguardClockSwitch extends RelativeLayout { } }); - mClockInAnim.start(); - mClockOutAnim.start(); - - mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y, - statusAreaYTranslation); - mStatusAreaAnim.setStartDelay(useLargeClock ? STATUS_AREA_START_DELAY_MILLIS : 0L); - mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS); - mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mStatusAreaAnim = new AnimatorSet(); + mStatusAreaAnim.setStartDelay(STATUS_AREA_START_DELAY_MILLIS); + mStatusAreaAnim.setDuration( + useLargeClock ? STATUS_AREA_MOVE_UP_MILLIS : STATUS_AREA_MOVE_DOWN_MILLIS); + mStatusAreaAnim.setInterpolator(Interpolators.EMPHASIZED); + mStatusAreaAnim.playTogether( + ObjectAnimator.ofFloat(mStatusArea, TRANSLATION_Y, statusAreaYTranslation), + ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation)); mStatusAreaAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { if (mStatusAreaAnim == animation) { @@ -294,6 +310,9 @@ public class KeyguardClockSwitch extends RelativeLayout { } } }); + + mClockInAnim.start(); + mClockOutAnim.start(); mStatusAreaAnim.start(); } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index 9d9a87d72e46..c684dc54c6fd 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -51,6 +51,12 @@ interface AuthenticationRepository { */ val isBypassEnabled: StateFlow<Boolean> + /** + * Number of consecutively failed authentication attempts. This resets to `0` when + * authentication succeeds. + */ + val failedAuthenticationAttempts: StateFlow<Int> + /** See [isUnlocked]. */ fun setUnlocked(isUnlocked: Boolean) @@ -59,6 +65,9 @@ interface AuthenticationRepository { /** See [isBypassEnabled]. */ fun setBypassEnabled(isBypassEnabled: Boolean) + + /** See [failedAuthenticationAttempts]. */ + fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) } class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository { @@ -75,6 +84,10 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow() + private val _failedAuthenticationAttempts = MutableStateFlow(0) + override val failedAuthenticationAttempts: StateFlow<Int> = + _failedAuthenticationAttempts.asStateFlow() + override fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } @@ -86,6 +99,10 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) { _authenticationMethod.value = authenticationMethod } + + override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) { + _failedAuthenticationAttempts.value = failedAuthenticationAttempts + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 5aea930401d9..3984627a181d 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -75,6 +75,12 @@ constructor( */ val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled + /** + * Number of consecutively failed authentication attempts. This resets to `0` when + * authentication succeeds. + */ + val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts + init { // UNLOCKS WHEN AUTH METHOD REMOVED. // @@ -130,7 +136,12 @@ constructor( } if (isSuccessful) { + repository.setFailedAuthenticationAttempts(0) repository.setUnlocked(true) + } else { + repository.setFailedAuthenticationAttempts( + repository.failedAuthenticationAttempts.value + 1 + ) } return isSuccessful diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index 83250b638424..6f008c3017b9 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -36,8 +36,10 @@ sealed class AuthenticationMethodModel( data class Password(val password: String) : AuthenticationMethodModel(isSecure = true) - data class Pattern(val coordinates: List<PatternCoordinate>) : - AuthenticationMethodModel(isSecure = true) { + data class Pattern( + val coordinates: List<PatternCoordinate>, + val isPatternVisible: Boolean = true, + ) : AuthenticationMethodModel(isSecure = true) { data class PatternCoordinate( val x: Int, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt index 1404053e4618..682888fc39b5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt @@ -21,42 +21,43 @@ import android.content.Context import com.airbnb.lottie.LottieAnimationView import com.android.systemui.R import com.android.systemui.biometrics.AuthBiometricView.BiometricState -import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION /** Face/Fingerprint combined icon animator for BiometricPrompt. */ -class AuthBiometricFingerprintAndFaceIconController( - context: Context, - iconView: LottieAnimationView, - iconViewOverlay: LottieAnimationView +open class AuthBiometricFingerprintAndFaceIconController( + context: Context, + iconView: LottieAnimationView, + iconViewOverlay: LottieAnimationView, ) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) { override val actsAsConfirmButton: Boolean = true override fun shouldAnimateIconViewForTransition( - @BiometricState oldState: Int, - @BiometricState newState: Int + @BiometricState oldState: Int, + @BiometricState newState: Int ): Boolean = when (newState) { STATE_PENDING_CONFIRMATION -> true - STATE_AUTHENTICATED -> false else -> super.shouldAnimateIconViewForTransition(oldState, newState) } @RawRes override fun getAnimationForTransition( - @BiometricState oldState: Int, - @BiometricState newState: Int + @BiometricState oldState: Int, + @BiometricState newState: Int ): Int? = when (newState) { STATE_PENDING_CONFIRMATION -> { if (oldState == STATE_ERROR || oldState == STATE_HELP) { R.raw.fingerprint_dialogue_error_to_unlock_lottie + } else if (oldState == STATE_PENDING_CONFIRMATION) { + // TODO(jbolinger): missing asset for this transition + // (unlocked icon to success checkmark) + R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie } else { R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie } } - STATE_AUTHENTICATED -> null else -> super.getAnimationForTransition(oldState, newState) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt index 57ffd240065d..7ce74dbe91b4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt @@ -40,11 +40,11 @@ class AuthBiometricFingerprintAndFaceView( override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int, unsuccessfulReason: String) = modality == TYPE_FACE && !(isFaceClass3 && isLockoutErrorString(unsuccessfulReason)) - override fun onPointerDown(failedModalities: Set<Int>) = failedModalities.contains(TYPE_FACE) - override fun createIconController(): AuthIconController = AuthBiometricFingerprintAndFaceIconController(mContext, mIconView, mIconViewOverlay) + override fun isCoex() = true + private fun isLockoutErrorString(unsuccessfulReason: String) = unsuccessfulReason == FaceManager.getErrorString( mContext, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index f04fdfff67f1..9807b9e6f1b3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -122,7 +122,9 @@ open class AuthBiometricFingerprintIconController( if (shouldAnimateIconViewForTransition(lastState, newState)) { iconView.playAnimation() } - LottieColorUtils.applyDynamicColors(context, iconView) + if (isSideFps) { + LottieColorUtils.applyDynamicColors(context, iconView) + } } override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 4db371bed517..fb160f2a2f00 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -56,43 +56,42 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; -import java.util.Set; /** * Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers. */ -public abstract class AuthBiometricView extends LinearLayout { +public abstract class AuthBiometricView extends LinearLayout implements AuthBiometricViewAdapter { private static final String TAG = "AuthBiometricView"; /** * Authentication hardware idle. */ - protected static final int STATE_IDLE = 0; + public static final int STATE_IDLE = 0; /** * UI animating in, authentication hardware active. */ - protected static final int STATE_AUTHENTICATING_ANIMATING_IN = 1; + public static final int STATE_AUTHENTICATING_ANIMATING_IN = 1; /** * UI animated in, authentication hardware active. */ - protected static final int STATE_AUTHENTICATING = 2; + public static final int STATE_AUTHENTICATING = 2; /** * UI animated in, authentication hardware active. */ - protected static final int STATE_HELP = 3; + public static final int STATE_HELP = 3; /** * Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle. */ - protected static final int STATE_ERROR = 4; + public static final int STATE_ERROR = 4; /** * Authenticated, waiting for user confirmation. Authentication hardware idle. */ - protected static final int STATE_PENDING_CONFIRMATION = 5; + public static final int STATE_PENDING_CONFIRMATION = 5; /** * Authenticated, dialog animating away soon. */ - protected static final int STATE_AUTHENTICATED = 6; + public static final int STATE_AUTHENTICATED = 6; @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_HELP, @@ -102,13 +101,14 @@ public abstract class AuthBiometricView extends LinearLayout { /** * Callback to the parent when a user action has occurred. */ - interface Callback { + public interface Callback { int ACTION_AUTHENTICATED = 1; int ACTION_USER_CANCELED = 2; int ACTION_BUTTON_NEGATIVE = 3; int ACTION_BUTTON_TRY_AGAIN = 4; int ACTION_ERROR = 5; int ACTION_USE_DEVICE_CREDENTIAL = 6; + int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7; /** * When an action has occurred. The caller will only invoke this when the callback should @@ -268,6 +268,27 @@ public abstract class AuthBiometricView extends LinearLayout { /** Create the controller for managing the icons transitions during the prompt.*/ @NonNull protected abstract AuthIconController createIconController(); + + @Override + public AuthIconController getLegacyIconController() { + return mIconController; + } + + @Override + public void cancelAnimation() { + animate().cancel(); + } + + @Override + public View asView() { + return this; + } + + @Override + public boolean isCoex() { + return false; + } + void setPanelController(AuthPanelController panelController) { mPanelController = panelController; } @@ -544,12 +565,12 @@ public abstract class AuthBiometricView extends LinearLayout { mState = newState; } - void onOrientationChanged() { + public void onOrientationChanged() { // Update padding and AuthPanel outline by calling updateSize when the orientation changed. updateSize(mSize); } - public void onDialogAnimatedIn() { + public void onDialogAnimatedIn(boolean fingerprintWasStarted) { updateState(STATE_AUTHENTICATING); } @@ -597,18 +618,6 @@ public abstract class AuthBiometricView extends LinearLayout { } /** - * Fingerprint pointer down event. This does nothing by default and will not be called if the - * device does not have an appropriate sensor (UDFPS), but it may be used as an alternative - * to the "retry" button when fingerprint is used with other modalities. - * - * @param failedModalities the set of modalities that have failed - * @return true if a retry was initiated as a result of this event - */ - public boolean onPointerDown(Set<Integer> failedModalities) { - return false; - } - - /** * Show a help message to the user. * * @param modality sensor modality @@ -752,7 +761,8 @@ public abstract class AuthBiometricView extends LinearLayout { /** * Kicks off the animation process and invokes the callback. */ - void startTransitionToCredentialUI() { + @Override + public void startTransitionToCredentialUI() { updateSize(AuthDialog.SIZE_LARGE); mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt new file mode 100644 index 000000000000..631511c231e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt @@ -0,0 +1,56 @@ +/* + * 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.biometrics + +import android.hardware.biometrics.BiometricAuthenticator +import android.os.Bundle +import android.view.View + +/** TODO(b/251476085): Temporary interface while legacy biometric prompt is around. */ +@Deprecated("temporary adapter while migrating biometric prompt - do not expand") +interface AuthBiometricViewAdapter { + val legacyIconController: AuthIconController? + + fun onDialogAnimatedIn(fingerprintWasStarted: Boolean) + + fun onAuthenticationSucceeded(@BiometricAuthenticator.Modality modality: Int) + + fun onAuthenticationFailed( + @BiometricAuthenticator.Modality modality: Int, + failureReason: String + ) + + fun onError(@BiometricAuthenticator.Modality modality: Int, error: String) + + fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String) + + fun startTransitionToCredentialUI() + + fun requestLayout() + + fun onSaveState(bundle: Bundle?) + + fun restoreState(bundle: Bundle?) + + fun onOrientationChanged() + + fun cancelAnimation() + + fun isCoex(): Boolean + + fun asView(): View +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index e775c2e2a3fa..49ac26411d3e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -16,15 +16,13 @@ package com.android.systemui.biometrics; -import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; -import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION; import android.animation.Animator; -import android.annotation.DurationMillisLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -66,12 +64,19 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController.ScaleFactorProvider; -import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor; +import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; +import com.android.systemui.biometrics.domain.model.BiometricModalities; +import com.android.systemui.biometrics.ui.BiometricPromptLayout; import com.android.systemui.biometrics.ui.CredentialView; import com.android.systemui.biometrics.ui.binder.AuthBiometricFingerprintViewBinder; +import com.android.systemui.biometrics.ui.binder.BiometricViewBinder; import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; +import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -84,6 +89,8 @@ import java.util.Set; import javax.inject.Provider; +import kotlinx.coroutines.CoroutineScope; + /** * Top level container/controller for the BiometricPrompt UI. */ @@ -126,16 +133,20 @@ public class AuthContainerView extends LinearLayout private final WakefulnessLifecycle mWakefulnessLifecycle; private final AuthDialogPanelInteractionDetector mPanelInteractionDetector; private final InteractionJankMonitor mInteractionJankMonitor; + private final CoroutineScope mApplicationCoroutineScope; // TODO: these should be migrated out once ready - private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor; + private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor; private final Provider<AuthBiometricFingerprintViewModel> mAuthBiometricFingerprintViewModelProvider; + private final @NonNull Provider<PromptSelectorInteractor> mPromptSelectorInteractorProvider; + // TODO(b/251476085): these should be migrated out of the view private final Provider<CredentialViewModel> mCredentialViewModelProvider; + private final PromptViewModel mPromptViewModel; @VisibleForTesting final BiometricCallback mBiometricCallback; - @Nullable private AuthBiometricView mBiometricView; + @Nullable private AuthBiometricViewAdapter mBiometricView; @Nullable private View mCredentialView; private final AuthPanelController mPanelController; private final FrameLayout mFrameLayout; @@ -154,7 +165,8 @@ public class AuthContainerView extends LinearLayout // HAT received from LockSettingsService when credential is verified. @Nullable private byte[] mCredentialAttestation; - @VisibleForTesting + // TODO(b/251476085): remove when legacy prompt is replaced + @Deprecated static class Config { Context mContext; AuthDialogCallback mCallback; @@ -167,96 +179,9 @@ public class AuthContainerView extends LinearLayout long mOperationId; long mRequestId = -1; boolean mSkipAnimation = false; - @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT; ScaleFactorProvider mScaleProvider; } - public static class Builder { - Config mConfig; - - public Builder(Context context) { - mConfig = new Config(); - mConfig.mContext = context; - } - - public Builder setCallback(AuthDialogCallback callback) { - mConfig.mCallback = callback; - return this; - } - - public Builder setPromptInfo(PromptInfo promptInfo) { - mConfig.mPromptInfo = promptInfo; - return this; - } - - public Builder setRequireConfirmation(boolean requireConfirmation) { - mConfig.mRequireConfirmation = requireConfirmation; - return this; - } - - public Builder setUserId(int userId) { - mConfig.mUserId = userId; - return this; - } - - public Builder setOpPackageName(String opPackageName) { - mConfig.mOpPackageName = opPackageName; - return this; - } - - public Builder setSkipIntro(boolean skip) { - mConfig.mSkipIntro = skip; - return this; - } - - public Builder setOperationId(@DurationMillisLong long operationId) { - mConfig.mOperationId = operationId; - return this; - } - - /** Unique id for this request. */ - public Builder setRequestId(long requestId) { - mConfig.mRequestId = requestId; - return this; - } - - @VisibleForTesting - public Builder setSkipAnimationDuration(boolean skip) { - mConfig.mSkipAnimation = skip; - return this; - } - - /** The multi-sensor mode. */ - public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) { - mConfig.mMultiSensorConfig = multiSensorConfig; - return this; - } - - public Builder setScaleFactorProvider(ScaleFactorProvider scaleProvider) { - mConfig.mScaleProvider = scaleProvider; - return this; - } - - public AuthContainerView build(@Background DelayableExecutor bgExecutor, int[] sensorIds, - @Nullable List<FingerprintSensorPropertiesInternal> fpProps, - @Nullable List<FaceSensorPropertiesInternal> faceProps, - @NonNull WakefulnessLifecycle wakefulnessLifecycle, - @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, - @NonNull UserManager userManager, - @NonNull LockPatternUtils lockPatternUtils, - @NonNull InteractionJankMonitor jankMonitor, - @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor, - @NonNull Provider<AuthBiometricFingerprintViewModel> - authBiometricFingerprintViewModelProvider, - @NonNull Provider<CredentialViewModel> credentialViewModelProvider) { - mConfig.mSensorIds = sensorIds; - return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle, - panelInteractionDetector, userManager, lockPatternUtils, jankMonitor, - biometricPromptInteractor, authBiometricFingerprintViewModelProvider, - credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor); - } - } - @VisibleForTesting final class BiometricCallback implements AuthBiometricView.Callback { @Override @@ -285,6 +210,9 @@ public class AuthContainerView extends LinearLayout addCredentialView(false /* animatePanel */, true /* animateContents */); }, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS); break; + case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR: + mConfig.mCallback.onStartFingerprintNow(getRequestId()); + break; default: Log.e(TAG, "Unhandled action: " + action); } @@ -336,8 +264,35 @@ public class AuthContainerView extends LinearLayout alertDialog.show(); } + // TODO(b/251476085): remove Config and further decompose these properties out of view classes + AuthContainerView(@NonNull Config config, + @NonNull FeatureFlags featureFlags, + @NonNull CoroutineScope applicationCoroutineScope, + @Nullable List<FingerprintSensorPropertiesInternal> fpProps, + @Nullable List<FaceSensorPropertiesInternal> faceProps, + @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, + @NonNull UserManager userManager, + @NonNull LockPatternUtils lockPatternUtils, + @NonNull InteractionJankMonitor jankMonitor, + @NonNull Provider<AuthBiometricFingerprintViewModel> + authBiometricFingerprintViewModelProvider, + @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractor, + @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor, + @NonNull PromptViewModel promptViewModel, + @NonNull Provider<CredentialViewModel> credentialViewModelProvider, + @NonNull @Background DelayableExecutor bgExecutor) { + this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps, + wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, + jankMonitor, authBiometricFingerprintViewModelProvider, promptSelectorInteractor, + promptCredentialInteractor, promptViewModel, credentialViewModelProvider, + new Handler(Looper.getMainLooper()), bgExecutor); + } + @VisibleForTesting - AuthContainerView(Config config, + AuthContainerView(@NonNull Config config, + @NonNull FeatureFlags featureFlags, + @NonNull CoroutineScope applicationCoroutineScope, @Nullable List<FingerprintSensorPropertiesInternal> fpProps, @Nullable List<FaceSensorPropertiesInternal> faceProps, @NonNull WakefulnessLifecycle wakefulnessLifecycle, @@ -345,9 +300,11 @@ public class AuthContainerView extends LinearLayout @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, - @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor, @NonNull Provider<AuthBiometricFingerprintViewModel> authBiometricFingerprintViewModelProvider, + @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, + @NonNull Provider<PromptCredentialInteractor> credentialInteractor, + @NonNull PromptViewModel promptViewModel, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull Handler mainHandler, @NonNull @Background DelayableExecutor bgExecutor) { @@ -360,6 +317,7 @@ public class AuthContainerView extends LinearLayout mWindowManager = mContext.getSystemService(WindowManager.class); mWakefulnessLifecycle = wakefulnessLifecycle; mPanelInteractionDetector = panelInteractionDetector; + mApplicationCoroutineScope = applicationCoroutineScope; mTranslationY = getResources() .getDimension(R.dimen.biometric_dialog_animation_translation_offset); @@ -376,10 +334,70 @@ public class AuthContainerView extends LinearLayout mPanelController = new AuthPanelController(mContext, mPanelView); mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; - mBiometricPromptInteractor = biometricPromptInteractor; + mPromptCredentialInteractor = credentialInteractor; mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider; + mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; mCredentialViewModelProvider = credentialViewModelProvider; + mPromptViewModel = promptViewModel; + + if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) { + showPrompt(config, layoutInflater, promptViewModel, + Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), + Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds)); + } else { + showLegacyPrompt(config, layoutInflater, fpProps, faceProps); + } + + // TODO: De-dupe the logic with AuthCredentialPasswordView + setOnKeyListener((v, keyCode, event) -> { + if (keyCode != KeyEvent.KEYCODE_BACK) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_UP) { + onBackInvoked(); + } + return true; + }); + + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + setFocusableInTouchMode(true); + requestFocus(); + } + + private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater, + @NonNull PromptViewModel viewModel, + @Nullable FingerprintSensorPropertiesInternal fpProps, + @Nullable FaceSensorPropertiesInternal faceProps) { + if (Utils.isBiometricAllowed(config.mPromptInfo)) { + mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication( + config.mPromptInfo, + config.mRequireConfirmation, + config.mUserId, + config.mOperationId, + new BiometricModalities(fpProps, faceProps)); + + final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( + R.layout.biometric_prompt_layout, null, false); + mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController, + // TODO(b/201510778): This uses the wrong timeout in some cases + getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), + mBackgroundView, mBiometricCallback, mApplicationCoroutineScope); + + // TODO(b/251476085): migrate these dependencies + if (fpProps != null && fpProps.isAnyUdfpsType()) { + view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps), + config.mScaleProvider); + } + } else { + mPromptSelectorInteractorProvider.get().resetPrompt(); + } + } + // TODO(b/251476085): remove entirely + private void showLegacyPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater, + @Nullable List<FingerprintSensorPropertiesInternal> fpProps, + @Nullable List<FaceSensorPropertiesInternal> faceProps + ) { // Inflate biometric view only if necessary. if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { final FingerprintSensorPropertiesInternal fpProperties = @@ -421,31 +439,18 @@ public class AuthContainerView extends LinearLayout // init view before showing if (mBiometricView != null) { - mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation); - mBiometricView.setPanelController(mPanelController); - mBiometricView.setPromptInfo(mConfig.mPromptInfo); - mBiometricView.setCallback(mBiometricCallback); - mBiometricView.setBackgroundView(mBackgroundView); - mBiometricView.setUserId(mConfig.mUserId); - mBiometricView.setEffectiveUserId(mEffectiveUserId); - mBiometricView.setJankListener(getJankListener(mBiometricView, TRANSIT, + final AuthBiometricView view = (AuthBiometricView) mBiometricView; + view.setRequireConfirmation(mConfig.mRequireConfirmation); + view.setPanelController(mPanelController); + view.setPromptInfo(mConfig.mPromptInfo); + view.setCallback(mBiometricCallback); + view.setBackgroundView(mBackgroundView); + view.setUserId(mConfig.mUserId); + view.setEffectiveUserId(mEffectiveUserId); + // TODO(b/201510778): This uses the wrong timeout in some cases (remove w/ above) + view.setJankListener(getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS)); } - - // TODO: De-dupe the logic with AuthCredentialPasswordView - setOnKeyListener((v, keyCode, event) -> { - if (keyCode != KeyEvent.KEYCODE_BACK) { - return false; - } - if (event.getAction() == KeyEvent.ACTION_UP) { - onBackInvoked(); - } - return true; - }); - - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - setFocusableInTouchMode(true); - requestFocus(); } private void onBackInvoked() { @@ -495,7 +500,7 @@ public class AuthContainerView extends LinearLayout mBackgroundView.setOnClickListener(null); mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - mBiometricPromptInteractor.get().useCredentialsForAuthentication( + mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication( mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId); final CredentialViewModel vm = mCredentialViewModelProvider.get(); vm.setAnimateContents(animateContents); @@ -527,7 +532,7 @@ public class AuthContainerView extends LinearLayout () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { - mBiometricScrollView.addView(mBiometricView); + mBiometricScrollView.addView(mBiometricView.asView()); } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true /* animatePanel */, false /* animateContents */); } else { @@ -601,9 +606,13 @@ public class AuthContainerView extends LinearLayout } private static boolean shouldUpdatePositionForUdfps(@NonNull View view) { + // TODO(b/251476085): legacy view (delete when removed) if (view instanceof AuthBiometricFingerprintView) { return ((AuthBiometricFingerprintView) view).isUdfps(); } + if (view instanceof BiometricPromptLayout) { + return ((BiometricPromptLayout) view).isUdfps(); + } return false; } @@ -613,7 +622,7 @@ public class AuthContainerView extends LinearLayout if (display == null) { return false; } - if (!shouldUpdatePositionForUdfps(mBiometricView)) { + if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) { return false; } @@ -626,12 +635,12 @@ public class AuthContainerView extends LinearLayout case Surface.ROTATION_90: mPanelController.setPosition(AuthPanelController.POSITION_RIGHT); - setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); + setScrollViewGravity(Gravity.BOTTOM | Gravity.RIGHT); break; case Surface.ROTATION_270: mPanelController.setPosition(AuthPanelController.POSITION_LEFT); - setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + setScrollViewGravity(Gravity.BOTTOM | Gravity.LEFT); break; case Surface.ROTATION_180: @@ -689,7 +698,7 @@ public class AuthContainerView extends LinearLayout mCredentialView.animate().cancel(); } mPanelView.animate().cancel(); - mBiometricView.animate().cancel(); + mBiometricView.cancelAnimation(); animate().cancel(); onDialogAnimatedIn(); } @@ -750,8 +759,9 @@ public class AuthContainerView extends LinearLayout @Override public void onPointerDown() { if (mBiometricView != null) { - if (mBiometricView.onPointerDown(mFailedModalities)) { + if (mFailedModalities.contains(TYPE_FACE)) { Log.d(TAG, "retrying failed modalities (pointer down)"); + mFailedModalities.remove(TYPE_FACE); mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN); } } else { @@ -885,11 +895,17 @@ public class AuthContainerView extends LinearLayout } mContainerState = STATE_SHOWING; if (mBiometricView != null) { - mConfig.mCallback.onDialogAnimatedIn(getRequestId()); - mBiometricView.onDialogAnimatedIn(); + final boolean delayFingerprint = mBiometricView.isCoex() && !mConfig.mRequireConfirmation; + mConfig.mCallback.onDialogAnimatedIn(getRequestId(), !delayFingerprint); + mBiometricView.onDialogAnimatedIn(!delayFingerprint); } } + @Override + public PromptViewModel getViewModel() { + return mPromptViewModel; + } + @VisibleForTesting static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) { final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED @@ -922,26 +938,5 @@ public class AuthContainerView extends LinearLayout if (mConfig != null) { pw.println(" config.sensorIds exist=" + (mConfig.mSensorIds != null)); } - final AuthBiometricView biometricView = mBiometricView; - pw.println(" scrollView=" + findViewById(R.id.biometric_scrollview)); - pw.println(" biometricView=" + biometricView); - if (biometricView != null) { - int[] ids = { - R.id.title, - R.id.subtitle, - R.id.description, - R.id.biometric_icon_frame, - R.id.biometric_icon, - R.id.indicator, - R.id.button_bar, - R.id.button_negative, - R.id.button_use_credential, - R.id.button_confirm, - R.id.button_try_again - }; - for (final int id: ids) { - pw.println(" " + biometricView.findViewById(id)); - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index fd9cee0d6144..57f1928fe545 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -37,7 +37,6 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; -import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.IBiometricContextListener; @@ -71,14 +70,18 @@ import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.udfps.UdfpsOverlayParams; import com.android.settingslib.udfps.UdfpsUtils; import com.android.systemui.CoreStartable; -import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; +import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; +import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.data.repository.BiometricType; import com.android.systemui.statusbar.CommandQueue; @@ -86,8 +89,6 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; -import kotlin.Unit; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -101,6 +102,9 @@ import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; +import kotlin.Unit; +import kotlinx.coroutines.CoroutineScope; + /** * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the * appropriate biometric UI (e.g. BiometricDialogView). @@ -109,7 +113,7 @@ import javax.inject.Provider; * {@link com.android.keyguard.KeyguardUpdateMonitor} */ @SysUISingleton -public class AuthController implements CoreStartable, CommandQueue.Callbacks, +public class AuthController implements CoreStartable, CommandQueue.Callbacks, AuthDialogCallback, DozeReceiver { private static final String TAG = "AuthController"; @@ -118,6 +122,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, private final Handler mHandler; private final Context mContext; + private final FeatureFlags mFeatureFlags; private final Execution mExecution; private final CommandQueue mCommandQueue; private final ActivityTaskManager mActivityTaskManager; @@ -125,13 +130,15 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @Nullable private final FaceManager mFaceManager; private final Provider<UdfpsController> mUdfpsControllerFactory; private final Provider<SideFpsController> mSidefpsControllerFactory; + private final CoroutineScope mApplicationCoroutineScope; // TODO: these should be migrated out once ready - @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor; - @NonNull private final Provider<AuthBiometricFingerprintViewModel> mAuthBiometricFingerprintViewModelProvider; + @NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor; + @NonNull private final Provider<PromptSelectorInteractor> mPromptSelectorInteractor; @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider; + @NonNull private final Provider<PromptViewModel> mPromptViewModelProvider; @NonNull private final LogContextInteractor mLogContextInteractor; private final Display mDisplay; @@ -461,7 +468,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } @Override - public void onDialogAnimatedIn(long requestId) { + public void onDialogAnimatedIn(long requestId, boolean startFingerprintNow) { final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); if (receiver == null) { Log.w(TAG, "Skip onDialogAnimatedIn"); @@ -469,7 +476,22 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } try { - receiver.onDialogAnimatedIn(); + receiver.onDialogAnimatedIn(startFingerprintNow); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); + } + } + + @Override + public void onStartFingerprintNow(long requestId) { + final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); + if (receiver == null) { + Log.e(TAG, "onStartUdfpsNow: Receiver is null"); + return; + } + + try { + receiver.onStartFingerprintNow(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); } @@ -728,6 +750,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } @Inject public AuthController(Context context, + @NonNull FeatureFlags featureFlags, + @Application CoroutineScope applicationCoroutineScope, Execution execution, CommandQueue commandQueue, ActivityTaskManager activityTaskManager, @@ -743,16 +767,19 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull LockPatternUtils lockPatternUtils, @NonNull UdfpsLogger udfpsLogger, @NonNull LogContextInteractor logContextInteractor, - @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor, @NonNull Provider<AuthBiometricFingerprintViewModel> authBiometricFingerprintViewModelProvider, + @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractorProvider, + @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, + @NonNull Provider<PromptViewModel> promptViewModelProvider, @NonNull InteractionJankMonitor jankMonitor, @Main Handler handler, @Background DelayableExecutor bgExecutor, @NonNull VibratorHelper vibrator, @NonNull UdfpsUtils udfpsUtils) { mContext = context; + mFeatureFlags = featureFlags; mExecution = execution; mUserManager = userManager; mLockPatternUtils = lockPatternUtils; @@ -773,10 +800,13 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mFaceEnrolledForUser = new SparseBooleanArray(); mVibratorHelper = vibrator; mUdfpsUtils = udfpsUtils; + mApplicationCoroutineScope = applicationCoroutineScope; mLogContextInteractor = logContextInteractor; - mBiometricPromptInteractor = biometricPromptInteractor; mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider; + mPromptSelectorInteractor = promptSelectorInteractorProvider; + mPromptCredentialInteractor = promptCredentialInteractorProvider; + mPromptViewModelProvider = promptViewModelProvider; mCredentialViewModelProvider = credentialViewModelProvider; mOrientationListener = new BiometricDisplayListener( @@ -913,8 +943,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @Override public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, - int userId, long operationId, String opPackageName, long requestId, - @BiometricMultiSensorMode int multiSensorConfig) { + int userId, long operationId, String opPackageName, long requestId) { @Authenticators.Types final int authenticators = promptInfo.getAuthenticators(); if (DEBUG) { @@ -927,8 +956,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, + ", credentialAllowed: " + credentialAllowed + ", requireConfirmation: " + requireConfirmation + ", operationId: " + operationId - + ", requestId: " + requestId - + ", multiSensorConfig: " + multiSensorConfig); + + ", requestId: " + requestId); } SomeArgs args = SomeArgs.obtain(); args.arg1 = promptInfo; @@ -940,7 +968,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, args.arg6 = opPackageName; args.argl1 = operationId; args.argl2 = requestId; - args.argi2 = multiSensorConfig; boolean skipAnimation = false; if (mCurrentDialog != null) { @@ -948,7 +975,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, skipAnimation = true; } - showDialog(args, skipAnimation, null /* savedState */); + showDialog(args, skipAnimation, null /* savedState */, mPromptViewModelProvider.get()); } /** @@ -1171,7 +1198,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, return mFpEnrolledForUser.getOrDefault(userId, false); } - private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { + private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState, + @Nullable PromptViewModel viewModel) { mCurrentDialogArgs = args; final PromptInfo promptInfo = (PromptInfo) args.arg1; @@ -1182,7 +1210,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, final String opPackageName = (String) args.arg6; final long operationId = args.argl1; final long requestId = args.argl2; - @BiometricMultiSensorMode final int multiSensorConfig = args.argi2; // Create a new dialog but do not replace the current one yet. final AuthDialog newDialog = buildDialog( @@ -1195,11 +1222,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, skipAnimation, operationId, requestId, - multiSensorConfig, mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager, - mLockPatternUtils); + mLockPatternUtils, + viewModel); if (newDialog == null) { Log.e(TAG, "Unsupported type configuration"); @@ -1253,6 +1280,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, // Save the state of the current dialog (buttons showing, etc) if (mCurrentDialog != null) { + final PromptViewModel viewModel = mCurrentDialog.getViewModel(); final Bundle savedState = new Bundle(); mCurrentDialog.onSaveState(savedState); mCurrentDialog.dismissWithoutCallback(false /* animate */); @@ -1271,7 +1299,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL); } - showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); + showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState, viewModel); } } } @@ -1286,26 +1314,28 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, protected AuthDialog buildDialog(@Background DelayableExecutor bgExecutor, PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, - @BiometricMultiSensorMode int multiSensorConfig, @NonNull WakefulnessLifecycle wakefulnessLifecycle, @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, - @NonNull LockPatternUtils lockPatternUtils) { - return new AuthContainerView.Builder(mContext) - .setCallback(this) - .setPromptInfo(promptInfo) - .setRequireConfirmation(requireConfirmation) - .setUserId(userId) - .setOpPackageName(opPackageName) - .setSkipIntro(skipIntro) - .setOperationId(operationId) - .setRequestId(requestId) - .setMultiSensorConfig(multiSensorConfig) - .setScaleFactorProvider(() -> getScaleFactor()) - .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, - panelInteractionDetector, userManager, lockPatternUtils, - mInteractionJankMonitor, mBiometricPromptInteractor, - mAuthBiometricFingerprintViewModelProvider, mCredentialViewModelProvider); + @NonNull LockPatternUtils lockPatternUtils, + @NonNull PromptViewModel viewModel) { + final AuthContainerView.Config config = new AuthContainerView.Config(); + config.mContext = mContext; + config.mCallback = this; + config.mPromptInfo = promptInfo; + config.mRequireConfirmation = requireConfirmation; + config.mUserId = userId; + config.mOpPackageName = opPackageName; + config.mSkipIntro = skipIntro; + config.mOperationId = operationId; + config.mRequestId = requestId; + config.mSensorIds = sensorIds; + config.mScaleProvider = this::getScaleFactor; + return new AuthContainerView(config, mFeatureFlags, mApplicationCoroutineScope, mFpProps, mFaceProps, + wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, + mInteractionJankMonitor, mAuthBiometricFingerprintViewModelProvider, + mPromptCredentialInteractor, mPromptSelectorInteractor, viewModel, + mCredentialViewModelProvider, bgExecutor); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index 51f39b358659..b6eabfa76e36 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -24,13 +24,17 @@ import android.os.Bundle; import android.view.WindowManager; import com.android.systemui.Dumpable; +import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Interface for the biometric dialog UI. + * + * TODO(b/251476085): remove along with legacy controller once flag is removed */ +@Deprecated public interface AuthDialog extends Dumpable { String KEY_CONTAINER_GOING_AWAY = "container_going_away"; @@ -70,10 +74,10 @@ public interface AuthDialog extends Dumpable { * {@link AuthPanelController}. */ class LayoutParams { - final int mMediumHeight; - final int mMediumWidth; + public final int mMediumHeight; + public final int mMediumWidth; - LayoutParams(int mediumWidth, int mediumHeight) { + public LayoutParams(int mediumWidth, int mediumHeight) { mMediumWidth = mediumWidth; mMediumHeight = mediumHeight; } @@ -172,4 +176,6 @@ public interface AuthDialog extends Dumpable { * must remain fixed on the physical sensor location. */ void onOrientationChanged(); + + PromptViewModel getViewModel(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java index bbe461aaf6d9..9a2194025a1a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java @@ -70,5 +70,10 @@ public interface AuthDialogCallback { /** * Notifies when the dialog has finished animating. */ - void onDialogAnimatedIn(long requestId); + void onDialogAnimatedIn(long requestId, boolean startFingerprintNow); + + /** + * Notifies that the fingerprint sensor should be started now. + */ + void onStartFingerprintNow(long requestId); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt index f5f46405660f..f56bb881d8b7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt @@ -84,9 +84,6 @@ abstract class AuthIconController( } } - /** If the icon should act as a "retry" button in the [currentState]. */ - fun iconTapSendsRetryWhen(@BiometricState currentState: Int): Boolean = false - /** Call during [updateState] if the controller is not [deactivated]. */ abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java index ad100716eceb..acdde3404ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java @@ -114,16 +114,7 @@ public class AuthPanelController extends ViewOutlineProvider { } private int getTopBound(@Position int position) { - switch (position) { - case POSITION_BOTTOM: - return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin); - case POSITION_LEFT: - case POSITION_RIGHT: - return Math.max((mContainerHeight - mContentHeight) / 2, mMargin); - default: - Log.e(TAG, "Unrecognized position: " + position); - return getTopBound(POSITION_BOTTOM); - } + return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin); } public void setContainerDimensions(int containerWidth, int containerHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index 43745bf74aae..16dc42acde46 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -63,7 +63,7 @@ public class UdfpsDialogMeasureAdapter { } @NonNull - AuthDialog.LayoutParams onMeasureInternal( + public AuthDialog.LayoutParams onMeasureInternal( int width, int height, @NonNull AuthDialog.LayoutParams layoutParams, float scaleFactor) { @@ -86,7 +86,7 @@ public class UdfpsDialogMeasureAdapter { * too cleanly support this case. So, let's have the onLayout code translate the sensor location * instead. */ - int getBottomSpacerHeight() { + public int getBottomSpacerHeight() { return mBottomSpacerHeight; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index 096d94144480..ddf1457e385c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -31,6 +31,8 @@ import com.android.systemui.biometrics.domain.interactor.LogContextInteractor import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds @@ -57,6 +59,11 @@ interface BiometricsModule { @Binds @SysUISingleton + fun providesPromptSelectorInteractor(impl: PromptSelectorInteractorImpl): + PromptSelectorInteractor + + @Binds + @SysUISingleton fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor @Binds diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index 92a13cfe538b..b4dc272b71da 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -2,7 +2,7 @@ package com.android.systemui.biometrics.data.repository import android.hardware.biometrics.PromptInfo import com.android.systemui.biometrics.AuthController -import com.android.systemui.biometrics.data.model.PromptKind +import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -35,12 +35,20 @@ interface PromptRepository { /** The kind of credential to use (biometric, pin, pattern, etc.). */ val kind: StateFlow<PromptKind> + /** + * If explicit confirmation is required. + * + * Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up. + */ + val isConfirmationRequired: StateFlow<Boolean> + /** Update the prompt configuration, which should be set before [isShowing]. */ fun setPrompt( promptInfo: PromptInfo, userId: Int, gatekeeperChallenge: Long?, - kind: PromptKind = PromptKind.ANY_BIOMETRIC, + kind: PromptKind, + requireConfirmation: Boolean = false, ) /** Unset the prompt info. */ @@ -74,29 +82,35 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null) override val userId = _userId.asStateFlow() - private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.ANY_BIOMETRIC) + private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric()) override val kind = _kind.asStateFlow() + private val _isConfirmationRequired: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isConfirmationRequired = _isConfirmationRequired.asStateFlow() + override fun setPrompt( promptInfo: PromptInfo, userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, + requireConfirmation: Boolean, ) { _kind.value = kind _userId.value = userId _challenge.value = gatekeeperChallenge _promptInfo.value = promptInfo + _isConfirmationRequired.value = requireConfirmation } override fun unsetPrompt() { _promptInfo.value = null _userId.value = null _challenge.value = null - _kind.value = PromptKind.ANY_BIOMETRIC + _kind.value = PromptKind.Biometric() + _isConfirmationRequired.value = false } companion object { - private const val TAG = "BiometricPromptRepository" + private const val TAG = "PromptRepositoryImpl" } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index 6362c2f627d3..d92c2178046e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -1,14 +1,30 @@ +/* + * 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.biometrics.domain.interactor import android.hardware.biometrics.PromptInfo import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.systemui.biometrics.Utils -import com.android.systemui.biometrics.data.model.PromptKind import com.android.systemui.biometrics.data.repository.PromptRepository import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.domain.model.BiometricUserInfo +import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -24,8 +40,16 @@ import kotlinx.coroutines.withContext /** * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users * PIN, pattern, or password credential instead of a biometric. + * + * This is used to cache the calling app's options that were given to the underlying authenticate + * APIs and should be set before any UI is shown to the user. + * + * There can be at most one request active at a given time. Use [resetPrompt] when no request is + * active to clear the cache. + * + * Views that use any biometric should use [PromptSelectorInteractor] instead. */ -class BiometricPromptCredentialInteractor +class PromptCredentialInteractor @Inject constructor( @Background private val bgDispatcher: CoroutineDispatcher, @@ -36,7 +60,7 @@ constructor( val isShowing: Flow<Boolean> = biometricPromptRepository.isShowing /** Metadata about the current credential prompt, including app-supplied preferences. */ - val prompt: Flow<BiometricPromptRequest?> = + val prompt: Flow<BiometricPromptRequest.Credential?> = combine( biometricPromptRepository.promptInfo, biometricPromptRepository.challenge, @@ -48,20 +72,20 @@ constructor( } when (kind) { - PromptKind.PIN -> + PromptKind.Pin -> BiometricPromptRequest.Credential.Pin( info = promptInfo, userInfo = userInfo(userId), operationInfo = operationInfo(challenge) ) - PromptKind.PATTERN -> + PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern( info = promptInfo, userInfo = userInfo(userId), operationInfo = operationInfo(challenge), stealthMode = credentialInteractor.isStealthModeActive(userId) ) - PromptKind.PASSWORD -> + PromptKind.Password -> BiometricPromptRequest.Credential.Password( info = promptInfo, userInfo = userInfo(userId), @@ -182,8 +206,8 @@ constructor( /** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = when (this) { - Utils.CREDENTIAL_PIN -> PromptKind.PIN - Utils.CREDENTIAL_PASSWORD -> PromptKind.PASSWORD - Utils.CREDENTIAL_PATTERN -> PromptKind.PATTERN - else -> PromptKind.ANY_BIOMETRIC + Utils.CREDENTIAL_PIN -> PromptKind.Pin + Utils.CREDENTIAL_PASSWORD -> PromptKind.Password + Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern + else -> PromptKind.Biometric() } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt new file mode 100644 index 000000000000..e6e07f9d7794 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -0,0 +1,186 @@ +/* + * 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.biometrics.domain.interactor + +import android.hardware.biometrics.PromptInfo +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.Utils.getCredentialType +import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed +import com.android.systemui.biometrics.data.repository.PromptRepository +import com.android.systemui.biometrics.domain.model.BiometricModalities +import com.android.systemui.biometrics.domain.model.BiometricOperationInfo +import com.android.systemui.biometrics.domain.model.BiometricPromptRequest +import com.android.systemui.biometrics.domain.model.BiometricUserInfo +import com.android.systemui.biometrics.shared.model.PromptKind +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +/** + * Business logic for BiometricPrompt's biometric view variants (face, fingerprint, coex, etc.). + * + * This is used to cache the calling app's options that were given to the underlying authenticate + * APIs and should be set before any UI is shown to the user. + * + * There can be at most one request active at a given time. Use [resetPrompt] when no request is + * active to clear the cache. + * + * Views that use credential fallback should use [PromptCredentialInteractor] instead. + */ +interface PromptSelectorInteractor { + + /** Static metadata about the current prompt. */ + val prompt: Flow<BiometricPromptRequest.Biometric?> + + /** If using a credential is allowed. */ + val isCredentialAllowed: Flow<Boolean> + + /** + * The kind of credential the user may use as a fallback or [PromptKind.Biometric] if unknown or + * not [isCredentialAllowed]. + */ + val credentialKind: Flow<PromptKind> + + /** If the API caller requested explicit confirmation after successful authentication. */ + val isConfirmationRequested: Flow<Boolean> + + /** Use biometrics for authentication. */ + fun useBiometricsForAuthentication( + promptInfo: PromptInfo, + requireConfirmation: Boolean, + userId: Int, + challenge: Long, + modalities: BiometricModalities, + ) + + /** Use credential-based authentication instead of biometrics. */ + fun useCredentialsForAuthentication( + promptInfo: PromptInfo, + @Utils.CredentialType kind: Int, + userId: Int, + challenge: Long, + ) + + /** Unset the current authentication request. */ + fun resetPrompt() +} + +@SysUISingleton +class PromptSelectorInteractorImpl +@Inject +constructor( + private val promptRepository: PromptRepository, + lockPatternUtils: LockPatternUtils, +) : PromptSelectorInteractor { + + override val prompt: Flow<BiometricPromptRequest.Biometric?> = + combine( + promptRepository.promptInfo, + promptRepository.challenge, + promptRepository.userId, + promptRepository.kind + ) { promptInfo, challenge, userId, kind -> + if (promptInfo == null || userId == null || challenge == null) { + return@combine null + } + + when (kind) { + is PromptKind.Biometric -> + BiometricPromptRequest.Biometric( + info = promptInfo, + userInfo = BiometricUserInfo(userId = userId), + operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge), + modalities = kind.activeModalities, + ) + else -> null + } + } + + override val isConfirmationRequested: Flow<Boolean> = + promptRepository.promptInfo + .map { info -> info?.isConfirmationRequested ?: false } + .distinctUntilChanged() + + override val isCredentialAllowed: Flow<Boolean> = + promptRepository.promptInfo + .map { info -> if (info != null) isDeviceCredentialAllowed(info) else false } + .distinctUntilChanged() + + override val credentialKind: Flow<PromptKind> = + combine(prompt, isCredentialAllowed) { prompt, isAllowed -> + if (prompt != null && isAllowed) { + when ( + getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) + ) { + Utils.CREDENTIAL_PIN -> PromptKind.Pin + Utils.CREDENTIAL_PASSWORD -> PromptKind.Password + Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern + else -> PromptKind.Biometric() + } + } else { + PromptKind.Biometric() + } + } + + override fun useBiometricsForAuthentication( + promptInfo: PromptInfo, + requireConfirmation: Boolean, + userId: Int, + challenge: Long, + modalities: BiometricModalities + ) { + promptRepository.setPrompt( + promptInfo = promptInfo, + userId = userId, + gatekeeperChallenge = challenge, + kind = PromptKind.Biometric(modalities), + requireConfirmation = requireConfirmation, + ) + } + + override fun useCredentialsForAuthentication( + promptInfo: PromptInfo, + @Utils.CredentialType kind: Int, + userId: Int, + challenge: Long, + ) { + promptRepository.setPrompt( + promptInfo = promptInfo, + userId = userId, + gatekeeperChallenge = challenge, + kind = kind.asBiometricPromptCredential(), + ) + } + + override fun resetPrompt() { + promptRepository.unsetPrompt() + } +} + +// TODO(b/251476085): remove along with Utils.CredentialType +/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ +private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = + when (this) { + Utils.CREDENTIAL_PIN -> PromptKind.Pin + Utils.CREDENTIAL_PASSWORD -> PromptKind.Password + Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern + else -> PromptKind.Biometric() + } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt new file mode 100644 index 000000000000..274f58a5266b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt @@ -0,0 +1,55 @@ +/* + * 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.biometrics.domain.model + +import android.hardware.biometrics.SensorProperties +import android.hardware.face.FaceSensorPropertiesInternal +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal + +/** The available modalities for an operation. */ +data class BiometricModalities( + val fingerprintProperties: FingerprintSensorPropertiesInternal? = null, + val faceProperties: FaceSensorPropertiesInternal? = null, +) { + /** If there are no available modalities. */ + val isEmpty: Boolean + get() = !hasFingerprint && !hasFace + + /** If fingerprint authentication is available (and [fingerprintProperties] is non-null). */ + val hasFingerprint: Boolean + get() = fingerprintProperties != null + + /** If fingerprint authentication is available (and [faceProperties] is non-null). */ + val hasFace: Boolean + get() = faceProperties != null + + /** If only face authentication is enabled. */ + val hasFaceOnly: Boolean + get() = hasFace && !hasFingerprint + + /** If only fingerprint authentication is enabled. */ + val hasFingerprintOnly: Boolean + get() = hasFingerprint && !hasFace + + /** If face & fingerprint authentication is enabled (coex). */ + val hasFaceAndFingerprint: Boolean + get() = hasFingerprint && hasFace + + /** If [hasFace] and it is configured as a STRONG class 3 biometric. */ + val isFaceStrong: Boolean + get() = faceProperties?.sensorStrength == SensorProperties.STRENGTH_STRONG +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt new file mode 100644 index 000000000000..3197c0935d0b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt @@ -0,0 +1,35 @@ +/* + * 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.biometrics.domain.model + +import android.hardware.biometrics.BiometricAuthenticator + +/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI. */ +enum class BiometricModality { + None, + Fingerprint, + Face, +} + +/** Convert a framework [BiometricAuthenticator.Modality] to a SysUI [BiometricModality]. */ +@BiometricAuthenticator.Modality +fun Int.asBiometricModality(): BiometricModality = + when (this) { + BiometricAuthenticator.TYPE_FINGERPRINT -> BiometricModality.Fingerprint + BiometricAuthenticator.TYPE_FACE -> BiometricModality.Face + else -> BiometricModality.None + } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index 5ee0381db630..75de47d12427 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -21,6 +21,7 @@ sealed class BiometricPromptRequest( info: PromptInfo, userInfo: BiometricUserInfo, operationInfo: BiometricOperationInfo, + val modalities: BiometricModalities, ) : BiometricPromptRequest( title = info.title?.toString() ?: "", @@ -28,7 +29,9 @@ sealed class BiometricPromptRequest( description = info.description?.toString() ?: "", userInfo = userInfo, operationInfo = operationInfo - ) + ) { + val negativeButtonText: String = info.negativeButtonText?.toString() ?: "" + } /** Prompt using a credential (pin, pattern, password). */ sealed class Credential( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt index e82646f0d861..416fc64e69a3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.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. @@ -14,15 +14,19 @@ * limitations under the License. */ -package com.android.systemui.biometrics.data.model +package com.android.systemui.biometrics.shared.model import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.domain.model.BiometricModalities // TODO(b/251476085): this should eventually replace Utils.CredentialType /** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */ -enum class PromptKind { - ANY_BIOMETRIC, - PIN, - PATTERN, - PASSWORD, +sealed interface PromptKind { + data class Biometric( + val activeModalities: BiometricModalities = BiometricModalities(), + ) : PromptKind + + object Pin : PromptKind + object Pattern : PromptKind + object Password : PromptKind } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java new file mode 100644 index 000000000000..fb246cd51d8a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java @@ -0,0 +1,182 @@ +/* + * 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.biometrics.ui; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Insets; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.biometrics.AuthBiometricFingerprintIconController; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.AuthDialog; +import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter; + +import kotlin.Pair; + +/** + * Contains the Biometric views (title, subtitle, icon, buttons, etc.). + * + * TODO(b/251476085): get the udfps junk out of here, at a minimum. Likely can be replaced with a + * normal LinearLayout. + */ +public class BiometricPromptLayout extends LinearLayout { + + private static final String TAG = "BiometricPromptLayout"; + + @NonNull + private final WindowManager mWindowManager; + @Nullable + private AuthController.ScaleFactorProvider mScaleFactorProvider; + @Nullable + private UdfpsDialogMeasureAdapter mUdfpsAdapter; + + private final boolean mUseCustomBpSize; + private final int mCustomBpWidth; + private final int mCustomBpHeight; + + public BiometricPromptLayout(Context context) { + this(context, null); + } + + public BiometricPromptLayout(Context context, AttributeSet attrs) { + super(context, attrs); + + mWindowManager = context.getSystemService(WindowManager.class); + + mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size); + mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width); + mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height); + } + + @Deprecated + public void setUdfpsAdapter(@NonNull UdfpsDialogMeasureAdapter adapter, + @NonNull AuthController.ScaleFactorProvider scaleProvider) { + mUdfpsAdapter = adapter; + mScaleFactorProvider = scaleProvider != null ? scaleProvider : () -> 1.0f; + } + + @Deprecated + public boolean isUdfps() { + return mUdfpsAdapter != null; + } + + @Deprecated + public void updateFingerprintAffordanceSize( + @NonNull AuthBiometricFingerprintIconController iconController) { + if (mUdfpsAdapter != null) { + final int sensorDiameter = mUdfpsAdapter.getSensorDiameter( + mScaleFactorProvider.provide()); + iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter)); + } + } + + @NonNull + private AuthDialog.LayoutParams onMeasureInternal(int width, int height) { + int totalHeight = 0; + final int numChildren = getChildCount(); + for (int i = 0; i < numChildren; i++) { + final View child = getChildAt(i); + + if (child.getId() == R.id.space_above_icon + || child.getId() == R.id.space_below_icon + || child.getId() == R.id.button_bar) { + child.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, + MeasureSpec.EXACTLY)); + } else if (child.getId() == R.id.biometric_icon_frame) { + final View iconView = findViewById(R.id.biometric_icon); + child.measure( + MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().width, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height, + MeasureSpec.EXACTLY)); + } else if (child.getId() == R.id.biometric_icon) { + child.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + } else { + child.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + } + + if (child.getVisibility() != View.GONE) { + totalHeight += child.getMeasuredHeight(); + } + } + + final AuthDialog.LayoutParams params = new AuthDialog.LayoutParams(width, totalHeight); + if (mUdfpsAdapter != null) { + return mUdfpsAdapter.onMeasureInternal(width, height, params, + (mScaleFactorProvider != null) ? mScaleFactorProvider.provide() : 1.0f); + } else { + return params; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + if (mUseCustomBpSize) { + width = mCustomBpWidth; + height = mCustomBpHeight; + } else { + width = Math.min(width, height); + } + + // add nav bar insets since the parent AuthContainerView + // uses LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + final Insets insets = mWindowManager.getMaximumWindowMetrics().getWindowInsets() + .getInsets(WindowInsets.Type.navigationBars()); + final AuthDialog.LayoutParams params = onMeasureInternal(width, height); + setMeasuredDimension(params.mMediumWidth + insets.left + insets.right, + params.mMediumHeight + insets.bottom); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mUdfpsAdapter != null) { + // Move the UDFPS icon and indicator text if necessary. This probably only needs to + // happen for devices where the UDFPS sensor is too low. + // TODO(b/201510778): Update this logic to support cases where the sensor or text + // overlap the button bar area. + final float bottomSpacerHeight = mUdfpsAdapter.getBottomSpacerHeight(); + Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight); + if (bottomSpacerHeight < 0) { + final FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame); + iconFrame.setTranslationY(-bottomSpacerHeight); + final TextView indicator = findViewById(R.id.indicator); + indicator.setTranslationY(-bottomSpacerHeight); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt index ede62acb3255..a3f34ce7471d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt @@ -68,15 +68,15 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) : var inputTopBound: Int var headerRightBound = right var headerTopBounds = top + var headerBottomBounds = bottom val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) { inputTopBound = (bottom - credentialInput.height) / 2 inputLeftBound = (right - left) / 2 headerRightBound = inputLeftBound - headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset) - - if (descriptionView.bottom > bottomInset) { + if (descriptionView.bottom > headerBottomBounds) { + headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset) credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt new file mode 100644 index 000000000000..8486c3f96b21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -0,0 +1,622 @@ +/* + * 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.biometrics.ui.binder + +import android.animation.Animator +import android.content.Context +import android.hardware.biometrics.BiometricAuthenticator +import android.hardware.biometrics.BiometricConstants +import android.hardware.biometrics.BiometricPrompt +import android.hardware.face.FaceManager +import android.os.Bundle +import android.text.method.ScrollingMovementMethod +import android.util.Log +import android.view.View +import android.view.accessibility.AccessibilityManager +import android.widget.Button +import android.widget.TextView +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.android.systemui.R +import com.android.systemui.biometrics.AuthBiometricFaceIconController +import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController +import com.android.systemui.biometrics.AuthBiometricFingerprintIconController +import com.android.systemui.biometrics.AuthBiometricView +import com.android.systemui.biometrics.AuthBiometricView.Callback +import com.android.systemui.biometrics.AuthBiometricViewAdapter +import com.android.systemui.biometrics.AuthIconController +import com.android.systemui.biometrics.AuthPanelController +import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.domain.model.BiometricModalities +import com.android.systemui.biometrics.domain.model.BiometricModality +import com.android.systemui.biometrics.domain.model.asBiometricModality +import com.android.systemui.biometrics.shared.model.PromptKind +import com.android.systemui.biometrics.ui.BiometricPromptLayout +import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode +import com.android.systemui.biometrics.ui.viewmodel.PromptMessage +import com.android.systemui.biometrics.ui.viewmodel.PromptSize +import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +private const val TAG = "BiometricViewBinder" + +/** Top-most view binder for BiometricPrompt views. */ +object BiometricViewBinder { + + /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */ + @JvmStatic + fun bind( + view: BiometricPromptLayout, + viewModel: PromptViewModel, + panelViewController: AuthPanelController, + jankListener: BiometricJankListener, + backgroundView: View, + legacyCallback: Callback, + applicationScope: CoroutineScope, + ): AuthBiometricViewAdapter { + val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! + fun notifyAccessibilityChanged() { + Utils.notifyAccessibilityContentChanged(accessibilityManager, view) + } + + val textColorError = + view.resources.getColor(R.color.biometric_dialog_error, view.context.theme) + val textColorHint = + view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme) + + val titleView = view.findViewById<TextView>(R.id.title) + val subtitleView = view.findViewById<TextView>(R.id.subtitle) + val descriptionView = view.findViewById<TextView>(R.id.description) + + // set selected for marquee + titleView.isSelected = true + subtitleView.isSelected = true + descriptionView.movementMethod = ScrollingMovementMethod() + + val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay) + val iconView = view.findViewById<LottieAnimationView>(R.id.biometric_icon) + val indicatorMessageView = view.findViewById<TextView>(R.id.indicator) + + // Negative-side (left) buttons + val negativeButton = view.findViewById<Button>(R.id.button_negative) + val cancelButton = view.findViewById<Button>(R.id.button_cancel) + val credentialFallbackButton = view.findViewById<Button>(R.id.button_use_credential) + + // Positive-side (right) buttons + val confirmationButton = view.findViewById<Button>(R.id.button_confirm) + val retryButton = view.findViewById<Button>(R.id.button_try_again) + + // TODO(b/251476085): temporary workaround for the unsafe callbacks & legacy controllers + val adapter = + Spaghetti( + view = view, + viewModel = viewModel, + applicationContext = view.context.applicationContext, + applicationScope = applicationScope, + ) + + // bind to prompt + var boundSize = false + view.repeatWhenAttached { + // these do not change and need to be set before any size transitions + val modalities = viewModel.modalities.first() + titleView.text = viewModel.title.first() + descriptionView.text = viewModel.description.first() + subtitleView.text = viewModel.subtitle.first() + + // set button listeners + negativeButton.setOnClickListener { + legacyCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE) + } + cancelButton.setOnClickListener { + legacyCallback.onAction(Callback.ACTION_USER_CANCELED) + } + credentialFallbackButton.setOnClickListener { + viewModel.onSwitchToCredential() + legacyCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL) + } + confirmationButton.setOnClickListener { viewModel.confirmAuthenticated() } + retryButton.setOnClickListener { + viewModel.showAuthenticating(isRetry = true) + legacyCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN) + } + + // TODO(b/251476085): migrate legacy icon controllers and remove + var legacyState: Int = viewModel.legacyState.value + val iconController = + modalities.asIconController( + view.context, + iconView, + iconViewOverlay, + ) + adapter.attach(this, iconController, modalities, legacyCallback) + if (iconController is AuthBiometricFingerprintIconController) { + view.updateFingerprintAffordanceSize(iconController) + } + if (iconController is HackyCoexIconController) { + iconController.faceMode = !viewModel.isConfirmationRequested.first() + } + + // the icon controller must be created before this happens for the legacy + // sizing code in BiometricPromptLayout to work correctly. Simplify this + // when those are also migrated. (otherwise the icon size may not be set to + // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly + // used as part of the measure spec) + if (!boundSize) { + boundSize = true + BiometricViewSizeBinder.bind( + view = view, + viewModel = viewModel, + viewsToHideWhenSmall = + listOf( + titleView, + subtitleView, + descriptionView, + ), + viewsToFadeInOnSizeChange = + listOf( + titleView, + subtitleView, + descriptionView, + indicatorMessageView, + negativeButton, + cancelButton, + retryButton, + confirmationButton, + credentialFallbackButton, + ), + panelViewController = panelViewController, + jankListener = jankListener, + ) + } + + // TODO(b/251476085): migrate legacy icon controllers and remove + // The fingerprint sensor is started by the legacy + // AuthContainerView#onDialogAnimatedIn in all cases but the implicit coex flow + // (delayed mode). In that case, start it on the first transition to delayed + // which will be triggered by any auth failure. + lifecycleScope.launch { + val oldMode = viewModel.fingerprintStartMode.first() + viewModel.fingerprintStartMode.collect { newMode -> + // trigger sensor to start + if ( + oldMode == FingerprintStartMode.Pending && + newMode == FingerprintStartMode.Delayed + ) { + legacyCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR) + } + + if (newMode.isStarted) { + // do wonky switch from implicit to explicit flow + (iconController as? HackyCoexIconController)?.faceMode = false + viewModel.showAuthenticating( + modalities.asDefaultHelpMessage(view.context), + ) + } + } + } + + repeatOnLifecycle(Lifecycle.State.STARTED) { + // handle background clicks + launch { + combine(viewModel.isAuthenticated, viewModel.size) { (authenticated, _), size -> + when { + authenticated -> false + size == PromptSize.SMALL -> false + size == PromptSize.LARGE -> false + else -> true + } + } + .collect { dismissOnClick -> + backgroundView.setOnClickListener { + if (dismissOnClick) { + legacyCallback.onAction(Callback.ACTION_USER_CANCELED) + } else { + Log.w(TAG, "Ignoring background click") + } + } + } + } + + // set messages + launch { + viewModel.isIndicatorMessageVisible.collect { show -> + indicatorMessageView.visibility = show.asVisibleOrHidden() + } + } + + // configure & hide/disable buttons + launch { + viewModel.credentialKind + .map { kind -> + when (kind) { + PromptKind.Pin -> + view.resources.getString(R.string.biometric_dialog_use_pin) + PromptKind.Password -> + view.resources.getString(R.string.biometric_dialog_use_password) + PromptKind.Pattern -> + view.resources.getString(R.string.biometric_dialog_use_pattern) + else -> "" + } + } + .collect { credentialFallbackButton.text = it } + } + launch { viewModel.negativeButtonText.collect { negativeButton.text = it } } + launch { + viewModel.isConfirmButtonVisible.collect { show -> + confirmationButton.visibility = show.asVisibleOrGone() + } + } + launch { + viewModel.isCancelButtonVisible.collect { show -> + cancelButton.visibility = show.asVisibleOrGone() + } + } + launch { + viewModel.isNegativeButtonVisible.collect { show -> + negativeButton.visibility = show.asVisibleOrGone() + } + } + launch { + viewModel.isTryAgainButtonVisible.collect { show -> + retryButton.visibility = show.asVisibleOrGone() + } + } + launch { + viewModel.isCredentialButtonVisible.collect { show -> + credentialFallbackButton.visibility = show.asVisibleOrGone() + } + } + + // reuse the icon as a confirm button + launch { + viewModel.isConfirmButtonVisible + .map { isPending -> + when { + isPending && iconController.actsAsConfirmButton -> + View.OnClickListener { viewModel.confirmAuthenticated() } + else -> null + } + } + .collect { onClick -> + iconViewOverlay.setOnClickListener(onClick) + iconView.setOnClickListener(onClick) + } + } + + // TODO(b/251476085): remove w/ legacy icon controllers + // set icon affordance using legacy states + // like the old code, this causes animations to repeat on config changes :( + // but keep behavior for now as no one has complained... + launch { + viewModel.legacyState.collect { newState -> + iconController.updateState(legacyState, newState) + legacyState = newState + } + } + + // not sure why this is here, but the legacy code did it probably needed? + launch { + viewModel.isAuthenticating.collect { isAuthenticating -> + if (isAuthenticating) { + notifyAccessibilityChanged() + } + } + } + + // dismiss prompt when authenticated and confirmed + launch { + viewModel.isAuthenticated.collect { authState -> + if (authState.isAuthenticatedAndConfirmed) { + view.announceForAccessibility( + view.resources.getString(R.string.biometric_dialog_authenticated) + ) + notifyAccessibilityChanged() + + launch { + delay(authState.delay) + legacyCallback.onAction(Callback.ACTION_AUTHENTICATED) + } + } + } + } + + // show error & help messages + launch { + viewModel.message.collect { promptMessage -> + val isError = promptMessage is PromptMessage.Error + + indicatorMessageView.text = promptMessage.message + indicatorMessageView.setTextColor( + if (isError) textColorError else textColorHint + ) + + // select to enable marquee unless a screen reader is enabled + // TODO(wenhuiy): this may have recently changed per UX - verify and remove + indicatorMessageView.isSelected = + !accessibilityManager.isEnabled || + !accessibilityManager.isTouchExplorationEnabled + + notifyAccessibilityChanged() + } + } + } + } + + return adapter + } +} + +/** + * Adapter for legacy events. Remove once legacy controller can be replaced by flagged code. + * + * These events can be dispatched when the view is being recreated so they need to be delivered to + * the view model (which will be retained) via the application scope. + * + * Do not reference the [view] for anything other than [asView]. + * + * TODO(b/251476085): remove after replacing AuthContainerView + */ +private class Spaghetti( + private val view: View, + private val viewModel: PromptViewModel, + private val applicationContext: Context, + private val applicationScope: CoroutineScope, +) : AuthBiometricViewAdapter { + + private var lifecycleScope: CoroutineScope? = null + private var modalities: BiometricModalities = BiometricModalities() + private var faceFailedAtLeastOnce = false + private var legacyCallback: Callback? = null + + override var legacyIconController: AuthIconController? = null + private set + + // hacky way to suppress lockout errors + private val lockoutErrorStrings = + listOf( + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT, + ) + .map { FaceManager.getErrorString(applicationContext, it, 0 /* vendorCode */) } + + fun attach( + lifecycleOwner: LifecycleOwner, + iconController: AuthIconController, + activeModalities: BiometricModalities, + callback: Callback, + ) { + modalities = activeModalities + legacyIconController = iconController + legacyCallback = callback + + lifecycleOwner.lifecycle.addObserver( + object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + lifecycleScope = owner.lifecycleScope + iconController.deactivated = false + } + + override fun onDestroy(owner: LifecycleOwner) { + lifecycleScope = null + iconController.deactivated = true + } + } + ) + } + + override fun onDialogAnimatedIn(fingerprintWasStarted: Boolean) { + if (fingerprintWasStarted) { + viewModel.ensureFingerprintHasStarted(isDelayed = false) + viewModel.showAuthenticating(modalities.asDefaultHelpMessage(applicationContext)) + } else { + viewModel.showAuthenticating() + } + } + + override fun onAuthenticationSucceeded(@BiometricAuthenticator.Modality modality: Int) { + applicationScope.launch { + val authenticatedModality = modality.asBiometricModality() + val msgId = getHelpForSuccessfulAuthentication(authenticatedModality) + viewModel.showAuthenticated( + modality = authenticatedModality, + dismissAfterDelay = 500, + helpMessage = if (msgId != null) applicationContext.getString(msgId) else "" + ) + } + } + + private suspend fun getHelpForSuccessfulAuthentication( + authenticatedModality: BiometricModality, + ): Int? = + when { + // for coex, show a message when face succeeds after fingerprint has also started + modalities.hasFaceAndFingerprint && + (viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) && + (authenticatedModality == BiometricModality.Face) -> + R.string.biometric_dialog_tap_confirm_with_face + else -> null + } + + override fun onAuthenticationFailed( + @BiometricAuthenticator.Modality modality: Int, + failureReason: String, + ) { + val failedModality = modality.asBiometricModality() + viewModel.ensureFingerprintHasStarted(isDelayed = true) + + applicationScope.launch { + val suppress = + modalities.hasFaceAndFingerprint && + (failedModality == BiometricModality.Face) && + faceFailedAtLeastOnce + if (failedModality == BiometricModality.Face) { + faceFailedAtLeastOnce = true + } + + viewModel.showTemporaryError( + failureReason, + messageAfterError = modalities.asDefaultHelpMessage(applicationContext), + authenticateAfterError = modalities.hasFingerprint, + suppressIfErrorShowing = suppress, + failedModality = failedModality, + ) + } + } + + override fun onError(modality: Int, error: String) { + val errorModality = modality.asBiometricModality() + if (ignoreUnsuccessfulEventsFrom(errorModality, error)) { + return + } + + applicationScope.launch { + val suppress = + modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face) + viewModel.showTemporaryError( + error, + suppressIfErrorShowing = suppress, + ) + delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong()) + legacyCallback?.onAction(Callback.ACTION_ERROR) + } + } + + override fun onHelp(modality: Int, help: String) { + if (ignoreUnsuccessfulEventsFrom(modality.asBiometricModality(), "")) { + return + } + + applicationScope.launch { + viewModel.showTemporaryHelp( + help, + messageAfterHelp = modalities.asDefaultHelpMessage(applicationContext), + ) + } + } + + private fun ignoreUnsuccessfulEventsFrom(modality: BiometricModality, message: String) = + when { + modalities.hasFaceAndFingerprint -> + (modality == BiometricModality.Face) && + !(modalities.isFaceStrong && lockoutErrorStrings.contains(message)) + else -> false + } + + override fun startTransitionToCredentialUI() { + applicationScope.launch { + viewModel.onSwitchToCredential() + legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL) + } + } + + override fun requestLayout() { + // nothing, for legacy view... + } + + override fun restoreState(bundle: Bundle?) { + // nothing, for legacy view... + } + + override fun onSaveState(bundle: Bundle?) { + // nothing, for legacy view... + } + + override fun onOrientationChanged() { + // nothing, for legacy view... + } + + override fun cancelAnimation() { + view.animate()?.cancel() + } + + override fun isCoex() = modalities.hasFaceAndFingerprint + + override fun asView() = view +} + +private fun BiometricModalities.asDefaultHelpMessage(context: Context): String = + when { + hasFingerprint -> context.getString(R.string.fingerprint_dialog_touch_sensor) + else -> "" + } + +private fun BiometricModalities.asIconController( + context: Context, + iconView: LottieAnimationView, + iconViewOverlay: LottieAnimationView, +): AuthIconController = + when { + hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay) + hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) + hasFace -> AuthBiometricFaceIconController(context, iconView) + else -> throw IllegalStateException("unexpected view type :$this") + } + +private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE + +private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE + +// TODO(b/251476085): proper type? +typealias BiometricJankListener = Animator.AnimatorListener + +// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced +private class HackyCoexIconController( + context: Context, + iconView: LottieAnimationView, + iconViewOverlay: LottieAnimationView, +) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) { + + private var state: Int? = null + private val faceController = AuthBiometricFaceIconController(context, iconView) + + var faceMode: Boolean = true + set(value) { + if (field != value) { + field = value + + faceController.deactivated = !value + iconView.setImageIcon(null) + iconViewOverlay.setImageIcon(null) + state?.let { updateIcon(AuthBiometricView.STATE_IDLE, it) } + } + } + + override fun updateIcon(lastState: Int, newState: Int) { + if (deactivated) { + return + } + + if (faceMode) { + faceController.updateIcon(lastState, newState) + } else { + super.updateIcon(lastState, newState) + } + + state = newState + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt new file mode 100644 index 000000000000..e4c4e9aedb56 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -0,0 +1,209 @@ +/* + * 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.biometrics.ui.binder + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityManager +import android.widget.TextView +import androidx.core.animation.addListener +import androidx.core.view.doOnLayout +import androidx.lifecycle.lifecycleScope +import com.android.systemui.R +import com.android.systemui.biometrics.AuthDialog +import com.android.systemui.biometrics.AuthPanelController +import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.ui.BiometricPromptLayout +import com.android.systemui.biometrics.ui.viewmodel.PromptSize +import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel +import com.android.systemui.biometrics.ui.viewmodel.isLarge +import com.android.systemui.biometrics.ui.viewmodel.isMedium +import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall +import com.android.systemui.biometrics.ui.viewmodel.isSmall +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.launch + +/** Helper for [BiometricViewBinder] to handle resize transitions. */ +object BiometricViewSizeBinder { + + /** Resizes [BiometricPromptLayout] and the [panelViewController] via the [PromptViewModel]. */ + fun bind( + view: BiometricPromptLayout, + viewModel: PromptViewModel, + viewsToHideWhenSmall: List<TextView>, + viewsToFadeInOnSizeChange: List<View>, + panelViewController: AuthPanelController, + jankListener: BiometricJankListener, + ) { + val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! + fun notifyAccessibilityChanged() { + Utils.notifyAccessibilityContentChanged(accessibilityManager, view) + } + + fun startMonitoredAnimation(animators: List<Animator>) { + with(AnimatorSet()) { + addListener(jankListener) + addListener(onEnd = { notifyAccessibilityChanged() }) + play(animators.first()).apply { animators.drop(1).forEach { next -> with(next) } } + start() + } + } + + val iconHolderView = view.findViewById<View>(R.id.biometric_icon_frame) + val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding) + val fullSizeYOffset = + view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset) + + // cache the original position of the icon view (as done in legacy view) + // this must happen before any size changes can be made + var iconHolderOriginalY = 0f + view.doOnLayout { + iconHolderOriginalY = iconHolderView.y + + // bind to prompt + // TODO(b/251476085): migrate the legacy panel controller and simplify this + view.repeatWhenAttached { + var currentSize: PromptSize? = null + lifecycleScope.launch { + viewModel.size.collect { size -> + // prepare for animated size transitions + for (v in viewsToHideWhenSmall) { + v.showTextOrHide(forceHide = size.isSmall) + } + if (currentSize == null && size.isSmall) { + iconHolderView.alpha = 0f + } + if ((currentSize.isSmall && size.isMedium) || size.isSmall) { + viewsToFadeInOnSizeChange.forEach { it.alpha = 0f } + } + + // propagate size changes to legacy panel controller and animate transitions + view.doOnLayout { + val width = view.measuredWidth + val height = view.measuredHeight + + when { + size.isSmall -> { + iconHolderView.alpha = 1f + iconHolderView.y = + view.height - iconHolderView.height - iconPadding + val newHeight = + iconHolderView.height + 2 * iconPadding.toInt() - + iconHolderView.paddingTop - + iconHolderView.paddingBottom + panelViewController.updateForContentDimensions( + width, + newHeight, + 0, /* animateDurationMs */ + ) + } + size.isMedium && currentSize.isSmall -> { + val duration = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS + panelViewController.updateForContentDimensions( + width, + height, + duration, + ) + startMonitoredAnimation( + listOf( + iconHolderView.asVerticalAnimator( + duration = duration.toLong(), + toY = iconHolderOriginalY, + ), + viewsToFadeInOnSizeChange.asFadeInAnimator( + duration = duration.toLong(), + delay = duration.toLong(), + ), + ) + ) + } + size.isMedium && currentSize.isNullOrNotSmall -> { + panelViewController.updateForContentDimensions( + width, + height, + 0, /* animateDurationMs */ + ) + } + size.isLarge -> { + val duration = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS + panelViewController.setUseFullScreen(true) + panelViewController.updateForContentDimensions( + panelViewController.containerWidth, + panelViewController.containerHeight, + duration, + ) + + startMonitoredAnimation( + listOf( + view.asVerticalAnimator( + duration.toLong() * 2 / 3, + toY = view.y - fullSizeYOffset + ), + listOf(view) + .asFadeInAnimator( + duration = duration.toLong() / 2, + delay = duration.toLong(), + ), + ) + ) + // TODO(b/251476085): clean up (copied from legacy) + if (view.isAttachedToWindow) { + val parent = view.parent as? ViewGroup + parent?.removeView(view) + } + } + } + + currentSize = size + notifyAccessibilityChanged() + } + } + } + } + } + } +} + +private fun TextView.showTextOrHide(forceHide: Boolean = false) { + visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE +} + +private fun View.asVerticalAnimator( + duration: Long, + toY: Float, + fromY: Float = this.y +): ValueAnimator { + val animator = ValueAnimator.ofFloat(fromY, toY) + animator.duration = duration + animator.addUpdateListener { y = it.animatedValue as Float } + return animator +} + +private fun List<View>.asFadeInAnimator(duration: Long, delay: Long): ValueAnimator { + forEach { it.alpha = 0f } + val animator = ValueAnimator.ofFloat(0f, 1f) + animator.duration = duration + animator.startDelay = delay + animator.addUpdateListener { + val alpha = it.animatedValue as Float + forEach { view -> view.alpha = alpha } + } + return animator +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt index ba23f1cfa22d..a64798cba745 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt @@ -4,7 +4,7 @@ import android.graphics.drawable.Drawable import android.os.UserHandle /** View model for the top-level header / info area of BiometricPrompt. */ -interface HeaderViewModel { +interface CredentialHeaderViewModel { val user: UserHandle val title: String val subtitle: String diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt index 84bbceb38fa7..9d7b94081145 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt @@ -7,8 +7,8 @@ import android.text.InputType import com.android.internal.widget.LockPatternView import com.android.systemui.R import com.android.systemui.biometrics.Utils -import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor import com.android.systemui.biometrics.domain.interactor.CredentialStatus +import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject @@ -27,11 +27,11 @@ class CredentialViewModel @Inject constructor( @Application private val applicationContext: Context, - private val credentialInteractor: BiometricPromptCredentialInteractor, + private val credentialInteractor: PromptCredentialInteractor, ) { /** Top level information about the prompt. */ - val header: Flow<HeaderViewModel> = + val header: Flow<CredentialHeaderViewModel> = credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map { request -> BiometricPromptHeaderViewModelImpl( @@ -109,12 +109,14 @@ constructor( } /** Check a PIN or password and update [validatedAttestation] or [remainingAttempts]. */ - suspend fun checkCredential(text: CharSequence, header: HeaderViewModel) = + suspend fun checkCredential(text: CharSequence, header: CredentialHeaderViewModel) = checkCredential(credentialInteractor.checkCredential(header.asRequest(), text = text)) /** Check a pattern and update [validatedAttestation] or [remainingAttempts]. */ - suspend fun checkCredential(pattern: List<LockPatternView.Cell>, header: HeaderViewModel) = - checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern)) + suspend fun checkCredential( + pattern: List<LockPatternView.Cell>, + header: CredentialHeaderViewModel + ) = checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern)) private suspend fun checkCredential(result: CredentialStatus) { when (result) { @@ -172,7 +174,7 @@ private class BiometricPromptHeaderViewModelImpl( override val subtitle: String, override val description: String, override val icon: Drawable, -) : HeaderViewModel +) : CredentialHeaderViewModel -private fun HeaderViewModel.asRequest(): BiometricPromptRequest.Credential = +private fun CredentialHeaderViewModel.asRequest(): BiometricPromptRequest.Credential = (this as BiometricPromptHeaderViewModelImpl).request diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt new file mode 100644 index 000000000000..9cb91b3d51a7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt @@ -0,0 +1,56 @@ +/* + * 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.biometrics.ui.viewmodel + +import com.android.systemui.biometrics.domain.model.BiometricModality + +/** + * The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an + * optional [delay] to keep the UI showing before dismissing when [needsUserConfirmation] is not + * required. + */ +data class PromptAuthState( + val isAuthenticated: Boolean, + val authenticatedModality: BiometricModality = BiometricModality.None, + val needsUserConfirmation: Boolean = false, + val delay: Long = 0, +) { + /** If authentication was successful and the user has confirmed (or does not need to). */ + val isAuthenticatedAndConfirmed: Boolean + get() = isAuthenticated && !needsUserConfirmation + + /** If a successful authentication has not occurred. */ + val isNotAuthenticated: Boolean + get() = !isAuthenticated + + /** If a authentication has succeeded and it was done by face (may need confirmation). */ + val isAuthenticatedByFace: Boolean + get() = isAuthenticated && authenticatedModality == BiometricModality.Face + + /** If a authentication has succeeded and it was done by fingerprint (may need confirmation). */ + val isAuthenticatedByFingerprint: Boolean + get() = isAuthenticated && authenticatedModality == BiometricModality.Fingerprint + + /** Copies this state, but toggles [needsUserConfirmation] to false. */ + fun asConfirmed(): PromptAuthState = + PromptAuthState( + isAuthenticated = isAuthenticated, + authenticatedModality = authenticatedModality, + needsUserConfirmation = false, + delay = delay, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt new file mode 100644 index 000000000000..219da716f7d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt @@ -0,0 +1,48 @@ +/* + * 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.biometrics.ui.viewmodel + +/** + * A help, hint, or error message to show. + * + * These typically correspond to the same category of help/error callbacks from the underlying HAL + * that runs the biometric operation, but may be customized by the framework. + */ +sealed interface PromptMessage { + + /** The message to show the user or the empty string. */ + val message: String + get() = + when (this) { + is Error -> errorMessage + is Help -> helpMessage + else -> "" + } + + /** If this is an [Error] or [Help] message. */ + val isErrorOrHelp: Boolean + get() = this is Error || this is Help + + /** An error message. */ + data class Error(val errorMessage: String) : PromptMessage + + /** A help message. */ + data class Help(val helpMessage: String) : PromptMessage + + /** No message. */ + object Empty : PromptMessage +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptSize.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptSize.kt new file mode 100644 index 000000000000..d779062be640 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptSize.kt @@ -0,0 +1,42 @@ +/* + * 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.biometrics.ui.viewmodel + +/** The size of a biometric prompt. */ +enum class PromptSize { + /** Minimal UI, showing only biometric icon. */ + SMALL, + /** Normal-sized biometric UI, showing title, icon, buttons, etc. */ + MEDIUM, + /** Full-screen credential UI. */ + LARGE, +} + +val PromptSize?.isSmall: Boolean + get() = this != null && this == PromptSize.SMALL + +val PromptSize?.isNotSmall: Boolean + get() = this != null && this != PromptSize.SMALL + +val PromptSize?.isNullOrNotSmall: Boolean + get() = this == null || this != PromptSize.SMALL + +val PromptSize?.isMedium: Boolean + get() = this != null && this == PromptSize.MEDIUM + +val PromptSize?.isLarge: Boolean + get() = this != null && this == PromptSize.LARGE diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt new file mode 100644 index 000000000000..2f8ed096f4ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -0,0 +1,453 @@ +/* + * 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.biometrics.ui.viewmodel + +import android.hardware.biometrics.BiometricPrompt +import android.util.Log +import com.android.systemui.biometrics.AuthBiometricView +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor +import com.android.systemui.biometrics.domain.model.BiometricModalities +import com.android.systemui.biometrics.domain.model.BiometricModality +import com.android.systemui.biometrics.shared.model.PromptKind +import javax.inject.Inject +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +/** ViewModel for BiometricPrompt. */ +class PromptViewModel +@Inject +constructor( + private val interactor: PromptSelectorInteractor, +) { + /** The set of modalities available for this prompt */ + val modalities: Flow<BiometricModalities> = + interactor.prompt.map { it?.modalities ?: BiometricModalities() }.distinctUntilChanged() + + // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state + private var _legacyState = MutableStateFlow(AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN) + val legacyState: StateFlow<Int> = _legacyState.asStateFlow() + + private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false) + + /** If the user is currently authenticating (i.e. at least one biometric is scanning). */ + val isAuthenticating: Flow<Boolean> = _isAuthenticating.asStateFlow() + + private val _isAuthenticated: MutableStateFlow<PromptAuthState> = + MutableStateFlow(PromptAuthState(false)) + + /** If the user has successfully authenticated and confirmed (when explicitly required). */ + val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow() + + /** If the API caller requested explicit confirmation after successful authentication. */ + val isConfirmationRequested: Flow<Boolean> = interactor.isConfirmationRequested + + /** The kind of credential the user has. */ + val credentialKind: Flow<PromptKind> = interactor.credentialKind + + /** The label to use for the cancel button. */ + val negativeButtonText: Flow<String> = interactor.prompt.map { it?.negativeButtonText ?: "" } + + private val _message: MutableStateFlow<PromptMessage> = MutableStateFlow(PromptMessage.Empty) + + /** A message to show the user, if there is an error, hint, or help to show. */ + val message: Flow<PromptMessage> = _message.asStateFlow() + + private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace } + + private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending) + + /** Fingerprint sensor state. */ + val fingerprintStartMode: Flow<FingerprintStartMode> = _fingerprintStartMode.asStateFlow() + + private val _forceLargeSize = MutableStateFlow(false) + private val _forceMediumSize = MutableStateFlow(false) + + /** The size of the prompt. */ + val size: Flow<PromptSize> = + combine( + _forceLargeSize, + _forceMediumSize, + modalities, + interactor.isConfirmationRequested, + fingerprintStartMode, + ) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode -> + when { + forceLarge -> PromptSize.LARGE + forceMedium -> PromptSize.MEDIUM + modalities.hasFaceOnly && !confirmationRequired -> PromptSize.SMALL + modalities.hasFaceAndFingerprint && + !confirmationRequired && + fpStartMode == FingerprintStartMode.Pending -> PromptSize.SMALL + else -> PromptSize.MEDIUM + } + } + .distinctUntilChanged() + + /** Title for the prompt. */ + val title: Flow<String> = interactor.prompt.map { it?.title ?: "" }.distinctUntilChanged() + + /** Subtitle for the prompt. */ + val subtitle: Flow<String> = interactor.prompt.map { it?.subtitle ?: "" }.distinctUntilChanged() + + /** Description for the prompt. */ + val description: Flow<String> = + interactor.prompt.map { it?.description ?: "" }.distinctUntilChanged() + + /** If the indicator (help, error) message should be shown. */ + val isIndicatorMessageVisible: Flow<Boolean> = + combine( + size, + message, + ) { size, message -> + size.isNotSmall && message.message.isNotBlank() + } + .distinctUntilChanged() + + /** If the auth is pending confirmation and the confirm button should be shown. */ + val isConfirmButtonVisible: Flow<Boolean> = + combine( + size, + isAuthenticated, + ) { size, authState -> + size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation + } + .distinctUntilChanged() + + /** If the negative button should be shown. */ + val isNegativeButtonVisible: Flow<Boolean> = + combine( + size, + isAuthenticated, + interactor.isCredentialAllowed, + ) { size, authState, credentialAllowed -> + size.isNotSmall && authState.isNotAuthenticated && !credentialAllowed + } + .distinctUntilChanged() + + /** If the cancel button should be shown (. */ + val isCancelButtonVisible: Flow<Boolean> = + combine( + size, + isAuthenticated, + isNegativeButtonVisible, + isConfirmButtonVisible, + ) { size, authState, showNegativeButton, showConfirmButton -> + size.isNotSmall && + authState.isAuthenticated && + !showNegativeButton && + showConfirmButton + } + .distinctUntilChanged() + + private val _canTryAgainNow = MutableStateFlow(false) + /** + * If authentication can be manually restarted via the try again button or touching a + * fingerprint sensor. + */ + val canTryAgainNow: Flow<Boolean> = + combine( + _canTryAgainNow, + size, + isAuthenticated, + isRetrySupported, + ) { readyToTryAgain, size, authState, supportsRetry -> + readyToTryAgain && size.isNotSmall && supportsRetry && authState.isNotAuthenticated + } + .distinctUntilChanged() + + /** If the try again button show be shown (only the button, see [canTryAgainNow]). */ + val isTryAgainButtonVisible: Flow<Boolean> = + combine( + canTryAgainNow, + modalities, + ) { tryAgainIsPossible, modalities -> + tryAgainIsPossible && modalities.hasFaceOnly + } + .distinctUntilChanged() + + /** If the credential fallback button show be shown. */ + val isCredentialButtonVisible: Flow<Boolean> = + combine( + size, + isAuthenticated, + interactor.isCredentialAllowed, + ) { size, authState, credentialAllowed -> + size.isNotSmall && authState.isNotAuthenticated && credentialAllowed + } + .distinctUntilChanged() + + private var messageJob: Job? = null + + /** + * Show a temporary error [message] associated with an optional [failedModality]. + * + * An optional [messageAfterError] will be shown via [showAuthenticating] when + * [authenticateAfterError] is set (or via [showHelp] when not set) after the error is + * dismissed. + * + * The error is ignored if the user has already authenticated and it is treated as + * [onSilentError] if [suppressIfErrorShowing] is set and an error message is already showing. + */ + suspend fun showTemporaryError( + message: String, + messageAfterError: String = "", + authenticateAfterError: Boolean = false, + suppressIfErrorShowing: Boolean = false, + failedModality: BiometricModality = BiometricModality.None, + ) = coroutineScope { + if (_isAuthenticated.value.isAuthenticated) { + return@coroutineScope + } + if (_message.value.isErrorOrHelp && suppressIfErrorShowing) { + onSilentError(failedModality) + return@coroutineScope + } + + _isAuthenticating.value = false + _isAuthenticated.value = PromptAuthState(false) + _forceMediumSize.value = true + _canTryAgainNow.value = supportsRetry(failedModality) + _message.value = PromptMessage.Error(message) + _legacyState.value = AuthBiometricView.STATE_ERROR + + messageJob?.cancel() + messageJob = launch { + delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong()) + if (authenticateAfterError) { + showAuthenticating(messageAfterError) + } else { + showHelp(messageAfterError) + } + } + } + + /** + * Call instead of [showTemporaryError] if an error from the HAL should be silently ignored to + * enable retry (if the [failedModality] supports retrying). + * + * Ignored if the user has already authenticated. + */ + private fun onSilentError(failedModality: BiometricModality = BiometricModality.None) { + if (_isAuthenticated.value.isNotAuthenticated) { + _canTryAgainNow.value = supportsRetry(failedModality) + } + } + + /** + * Call to ensure the fingerprint sensor has started. Either when the dialog is first shown + * (most cases) or when it should be enabled after a first error (coex implicit flow). + */ + fun ensureFingerprintHasStarted(isDelayed: Boolean) { + if (_fingerprintStartMode.value == FingerprintStartMode.Pending) { + _fingerprintStartMode.value = + if (isDelayed) FingerprintStartMode.Delayed else FingerprintStartMode.Normal + } + } + + // enable retry only when face fails (fingerprint runs constantly) + private fun supportsRetry(failedModality: BiometricModality) = + failedModality == BiometricModality.Face + + /** + * Show a persistent help message. + * + * Will be show even if the user has already authenticated. + */ + suspend fun showHelp(message: String) { + val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated + if (!alreadyAuthenticated) { + _isAuthenticating.value = false + _isAuthenticated.value = PromptAuthState(false) + } + + _message.value = + if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty + _forceMediumSize.value = true + _legacyState.value = + if (alreadyAuthenticated) { + AuthBiometricView.STATE_PENDING_CONFIRMATION + } else { + AuthBiometricView.STATE_HELP + } + + messageJob?.cancel() + messageJob = null + } + + /** + * Show a temporary help message and transition back to a fixed message. + * + * Ignored if the user has already authenticated. + */ + suspend fun showTemporaryHelp( + message: String, + messageAfterHelp: String = "", + ) = coroutineScope { + if (_isAuthenticated.value.isAuthenticated) { + return@coroutineScope + } + + _isAuthenticating.value = false + _isAuthenticated.value = PromptAuthState(false) + _message.value = + if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty + _forceMediumSize.value = true + _legacyState.value = AuthBiometricView.STATE_HELP + + messageJob?.cancel() + messageJob = launch { + delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong()) + showAuthenticating(messageAfterHelp) + } + } + + /** Show the user that biometrics are actively running and set [isAuthenticating]. */ + fun showAuthenticating(message: String = "", isRetry: Boolean = false) { + if (_isAuthenticated.value.isAuthenticated) { + // TODO(jbolinger): convert to go/tex-apc? + Log.w(TAG, "Cannot show authenticating after authenticated") + return + } + + _isAuthenticating.value = true + _isAuthenticated.value = PromptAuthState(false) + _message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message) + _legacyState.value = AuthBiometricView.STATE_AUTHENTICATING + + // reset the try again button(s) after the user attempts a retry + if (isRetry) { + _canTryAgainNow.value = false + } + + messageJob?.cancel() + messageJob = null + } + + /** + * Show successfully authentication, set [isAuthenticated], and dismiss the prompt after a + * [dismissAfterDelay] or prompt for explicit confirmation (if required). + */ + suspend fun showAuthenticated( + modality: BiometricModality, + dismissAfterDelay: Long, + helpMessage: String = "", + ) { + if (_isAuthenticated.value.isAuthenticated) { + // TODO(jbolinger): convert to go/tex-apc? + Log.w(TAG, "Cannot show authenticated after authenticated") + return + } + + _isAuthenticating.value = false + val needsUserConfirmation = needsExplicitConfirmation(modality) + _isAuthenticated.value = + PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay) + _message.value = PromptMessage.Empty + _legacyState.value = + if (needsUserConfirmation) { + AuthBiometricView.STATE_PENDING_CONFIRMATION + } else { + AuthBiometricView.STATE_AUTHENTICATED + } + + messageJob?.cancel() + messageJob = null + + if (helpMessage.isNotBlank()) { + showHelp(helpMessage) + } + } + + private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean { + val availableModalities = modalities.first() + val confirmationRequested = interactor.isConfirmationRequested.first() + + if (availableModalities.hasFaceAndFingerprint) { + // coex only needs confirmation when face is successful, unless it happens on the + // first attempt (i.e. without failure) before fingerprint scanning starts + if (modality == BiometricModality.Face) { + return (fingerprintStartMode.first() != FingerprintStartMode.Pending) || + confirmationRequested + } + } + if (availableModalities.hasFaceOnly) { + return confirmationRequested + } + // fingerprint only never requires confirmation + return false + } + + /** + * Set the prompt's auth state to authenticated and confirmed. + * + * This should only be used after [showAuthenticated] when the operation requires explicit user + * confirmation. + */ + fun confirmAuthenticated() { + val authState = _isAuthenticated.value + if (authState.isNotAuthenticated) { + "Cannot show authenticated after authenticated" + Log.w(TAG, "Cannot confirm authenticated when not authenticated") + return + } + + _isAuthenticated.value = authState.asConfirmed() + _message.value = PromptMessage.Empty + _legacyState.value = AuthBiometricView.STATE_AUTHENTICATED + + messageJob?.cancel() + messageJob = null + } + + /** + * Switch to the credential view. + * + * TODO(b/251476085): this should be decoupled from the shared panel controller + */ + fun onSwitchToCredential() { + _forceLargeSize.value = true + } + + companion object { + private const val TAG = "PromptViewModel" + } +} + +/** How the fingerprint sensor was started for the prompt. */ +enum class FingerprintStartMode { + /** Fingerprint sensor has not started. */ + Pending, + + /** Fingerprint sensor started immediately when prompt was displayed. */ + Normal, + + /** Fingerprint sensor started after the first failure of another passive modality. */ + Delayed; + + /** If this is [Normal] or [Delayed]. */ + val isStarted: Boolean + get() = this == Normal || this == Delayed +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt index 4c817b2e46a8..49a0a3c1e965 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.bouncer.data.repo +import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -29,7 +30,15 @@ class BouncerRepository @Inject constructor() { /** The user-facing message to show in the bouncer. */ val message: StateFlow<String?> = _message.asStateFlow() + private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null) + /** The current authentication throttling state. If `null`, there's no throttling. */ + val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow() + fun setMessage(message: String?) { _message.value = message } + + fun setThrottling(throttling: AuthenticationThrottledModel?) { + _throttling.value = throttling + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 8264fed4846a..e462e2f5b7d8 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -17,10 +17,12 @@ package com.android.systemui.bouncer.domain.interactor import android.content.Context +import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.data.repo.BouncerRepository +import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneKey @@ -29,8 +31,11 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Encapsulates business logic and application state accessing use-cases. */ @@ -46,7 +51,22 @@ constructor( ) { /** The user-facing message to show in the bouncer. */ - val message: StateFlow<String?> = repository.message + val message: StateFlow<String?> = + combine( + repository.message, + repository.throttling, + ) { message, throttling -> + messageOrThrottlingMessage(message, throttling) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = + messageOrThrottlingMessage( + repository.message.value, + repository.throttling.value, + ) + ) /** * The currently-configured authentication method. This determines how the authentication @@ -55,6 +75,9 @@ constructor( val authenticationMethod: StateFlow<AuthenticationMethodModel> = authenticationInteractor.authenticationMethod + /** The current authentication throttling state. If `null`, there's no throttling. */ + val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling + init { applicationScope.launch { combine( @@ -129,14 +152,39 @@ constructor( fun authenticate( input: List<Any>, ) { + if (repository.throttling.value != null) { + return + } + val isAuthenticated = authenticationInteractor.authenticate(input) - if (isAuthenticated) { - sceneInteractor.setCurrentScene( - containerName = containerName, - scene = SceneModel(SceneKey.Gone), - ) - } else { - repository.setMessage(errorMessage(authenticationMethod.value)) + val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value + when { + isAuthenticated -> { + repository.setThrottling(null) + sceneInteractor.setCurrentScene( + containerName = containerName, + scene = SceneModel(SceneKey.Gone), + ) + } + failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 -> + applicationScope.launch { + var remainingDurationSec = THROTTLE_DURATION_SEC + while (remainingDurationSec > 0) { + repository.setThrottling( + AuthenticationThrottledModel( + failedAttemptCount = failedAttempts, + totalDurationSec = THROTTLE_DURATION_SEC, + remainingDurationSec = remainingDurationSec, + ) + ) + remainingDurationSec-- + delay(1000) + } + + repository.setThrottling(null) + clearMessage() + } + else -> repository.setMessage(errorMessage(authenticationMethod.value)) } } @@ -163,10 +211,31 @@ constructor( } } + private fun messageOrThrottlingMessage( + message: String?, + throttling: AuthenticationThrottledModel?, + ): String { + return when { + throttling != null -> + applicationContext.getString( + com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown, + throttling.remainingDurationSec, + ) + message != null -> message + else -> "" + } + } + @AssistedFactory interface Factory { fun create( containerName: String, ): BouncerInteractor } + + companion object { + @VisibleForTesting const val THROTTLE_DURATION_SEC = 30 + @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15 + @VisibleForTesting const val THROTTLE_EVERY = 5 + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt new file mode 100644 index 000000000000..cbea635f6b13 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt @@ -0,0 +1,30 @@ +/* + * 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.bouncer.shared.model + +/** + * Models application state for when further authentication attempts are being throttled due to too + * many consecutive failed authentication attempts. + */ +data class AuthenticationThrottledModel( + /** Total number of failed attempts so far. */ + val failedAttemptCount: Int, + /** Total amount of time the user has to wait before attempting again. */ + val totalDurationSec: Int, + /** Remaining amount of time the user has to wait before attempting again. */ + val remainingDurationSec: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index ebefb78f0477..774a5593530c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -16,4 +16,14 @@ package com.android.systemui.bouncer.ui.viewmodel -sealed interface AuthMethodBouncerViewModel +import kotlinx.coroutines.flow.StateFlow + +sealed interface AuthMethodBouncerViewModel { + /** + * Whether user input is enabled. + * + * If `false`, user input should be completely ignored in the UI as the user is "locked out" of + * being able to attempt to unlock the device. + */ + val isInputEnabled: StateFlow<Boolean> +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index c6528d0736cd..02991bd47c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context +import com.android.systemui.R import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.qualifiers.Application @@ -24,10 +25,14 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** Holds UI state and handles user input on bouncer UIs. */ class BouncerViewModel @@ -40,16 +45,42 @@ constructor( ) { private val interactor: BouncerInteractor = interactorFactory.create(containerName) + /** + * Whether updates to the message should be cross-animated from one message to another. + * + * If `false`, no animation should be applied, the message text should just be replaced + * instantly. + */ + val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> = + interactor.throttling + .map { it == null } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = interactor.throttling.value == null, + ) + + private val isInputEnabled: StateFlow<Boolean> = + interactor.throttling + .map { it == null } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = interactor.throttling.value == null, + ) + private val pin: PinBouncerViewModel by lazy { PinBouncerViewModel( applicationScope = applicationScope, interactor = interactor, + isInputEnabled = isInputEnabled, ) } private val password: PasswordBouncerViewModel by lazy { PasswordBouncerViewModel( interactor = interactor, + isInputEnabled = isInputEnabled, ) } @@ -58,6 +89,7 @@ constructor( applicationContext = applicationContext, applicationScope = applicationScope, interactor = interactor, + isInputEnabled = isInputEnabled, ) } @@ -81,11 +113,59 @@ constructor( initialValue = interactor.message.value ?: "", ) + private val _throttlingDialogMessage = MutableStateFlow<String?>(null) + /** + * A message for a throttling dialog to show when the user has attempted the wrong credential + * too many times and now must wait a while before attempting again. + * + * If `null`, no dialog should be shown. + * + * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user + * dismisses this dialog. + */ + val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow() + + init { + applicationScope.launch { + interactor.throttling + .map { model -> + model?.let { + when (interactor.authenticationMethod.value) { + is AuthenticationMethodModel.PIN -> + R.string.kg_too_many_failed_pin_attempts_dialog_message + is AuthenticationMethodModel.Password -> + R.string.kg_too_many_failed_password_attempts_dialog_message + is AuthenticationMethodModel.Pattern -> + R.string.kg_too_many_failed_pattern_attempts_dialog_message + else -> null + }?.let { stringResourceId -> + applicationContext.getString( + stringResourceId, + model.failedAttemptCount, + model.totalDurationSec, + ) + } + } + } + .distinctUntilChanged() + .collect { dialogMessageOrNull -> + if (dialogMessageOrNull != null) { + _throttlingDialogMessage.value = dialogMessageOrNull + } + } + } + } + /** Notifies that the emergency services button was clicked. */ fun onEmergencyServicesButtonClicked() { // TODO(b/280877228): implement this } + /** Notifies that a throttling dialog has been dismissed by the user. */ + fun onThrottlingDialogDismissed() { + _throttlingDialogMessage.value = null + } + private fun toViewModel( authMethod: AuthenticationMethodModel, ): AuthMethodBouncerViewModel? { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 730d4e8ba050..c38fcaa3b657 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.asStateFlow /** Holds UI state and handles user input for the password bouncer UI. */ class PasswordBouncerViewModel( private val interactor: BouncerInteractor, + override val isInputEnabled: StateFlow<Boolean>, ) : AuthMethodBouncerViewModel { private val _password = MutableStateFlow("") diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index eb1b45771ad4..1b0b38ea6e9c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -37,6 +37,7 @@ class PatternBouncerViewModel( private val applicationContext: Context, applicationScope: CoroutineScope, private val interactor: BouncerInteractor, + override val isInputEnabled: StateFlow<Boolean>, ) : AuthMethodBouncerViewModel { /** The number of columns in the dot grid. */ @@ -63,6 +64,16 @@ class PatternBouncerViewModel( /** All dots on the grid. */ val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow() + /** Whether the pattern itself should be rendered visibly. */ + val isPatternVisible: StateFlow<Boolean> = + interactor.authenticationMethod + .map { authMethod -> isPatternVisible(authMethod) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = isPatternVisible(interactor.authenticationMethod.value), + ) + /** Notifies that the UI has been shown to the user. */ fun onShown() { interactor.resetMessage() @@ -146,6 +157,10 @@ class PatternBouncerViewModel( _selectedDots.value = linkedSetOf() } + private fun isPatternVisible(authMethodModel: AuthenticationMethodModel): Boolean { + return (authMethodModel as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false + } + private fun defaultDots(): List<PatternDotViewModel> { return buildList { (0 until columnCount).forEach { x -> diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index f9223cb0872e..2a733d93b857 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.launch class PinBouncerViewModel( private val applicationScope: CoroutineScope, private val interactor: BouncerInteractor, + override val isInputEnabled: StateFlow<Boolean>, ) : AuthMethodBouncerViewModel { private val entered = MutableStateFlow<List<Int>>(emptyList()) diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java index a334c1ed2338..0bdc7f1dfb96 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java @@ -77,6 +77,10 @@ public class ComplicationTypesUpdater extends ConditionalCoreStartable { Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, settingsObserver, UserHandle.myUserId()); + mSecureSettings.registerContentObserverForUser( + Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, + settingsObserver, + UserHandle.myUserId()); settingsObserver.onChange(false); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 61d6fede4bab..d8121926adba 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -100,6 +100,16 @@ object Flags { val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true) + // TODO(b/280783617): Tracking Bug + @Keep + @JvmField + val BUILDER_EXTRAS_OVERRIDE = + sysPropBooleanFlag( + 128, + "persist.sysui.notification.builder_extras_override", + default = false + ) + // 200 - keyguard/lockscreen // ** Flag retired ** // public static final BooleanFlag KEYGUARD_LAYOUT = @@ -132,7 +142,7 @@ object Flags { * the digits when the clock moves. */ @JvmField - val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation", teamfood = true) + val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation") /** * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository @@ -213,6 +223,7 @@ object Flags { ) /** Whether to use a new data source for intents to run on keyguard dismissal. */ + // TODO(b/275069969): Tracking bug. @JvmField val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent") @@ -236,13 +247,18 @@ object Flags { /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */ // TODO(b/279794160): Tracking bug. @JvmField - val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer") + val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer") /** 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") + /** Whether to listen for fingerprint authentication over keyguard occluding activities. */ + // TODO(b/283260512): Tracking bug. + @JvmField + val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -515,13 +531,6 @@ object Flags { val ENABLE_PIP_APP_ICON_OVERLAY = sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true) - // TODO(b/272110828): Tracking bug - @Keep - @JvmField - val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP = - sysPropBooleanFlag( - 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true) - // TODO(b/273443374): Tracking Bug @Keep @JvmField val LOCKSCREEN_LIVE_WALLPAPER = @@ -605,8 +614,6 @@ object Flags { unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false) // 1500 - chooser aka sharesheet - // TODO(b/254512507): Tracking Bug - val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled") // 1700 - clipboard @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") @@ -717,4 +724,12 @@ object Flags { @JvmField val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = unreleasedFlag(2805, "split_shade_subpixel_optimization", teamfood = true) + + // TODO(b/278761837): Tracking Bug + @JvmField + val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter") + + // TODO(b/283084712): Tracking Bug + @JvmField + val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 54da680d8a68..51a29b00f9db 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -19,7 +19,6 @@ package com.android.systemui.keyguard; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; @@ -30,13 +29,11 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionOldType; import static android.view.WindowManager.TransitionType; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.Service; @@ -116,6 +113,14 @@ public class KeyguardService extends Service { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); final int taskId = taskInfo != null ? change.getTaskInfo().taskId : -1; + if (taskId != -1 && change.getParent() != null) { + final TransitionInfo.Change parentChange = info.getChange(change.getParent()); + if (parentChange != null && parentChange.getTaskInfo() != null) { + // Only adding the root task as the animation target. + continue; + } + } + final RemoteAnimationTarget target = TransitionUtil.newTarget(change, // wallpapers go into the "below" layer space info.getChanges().size() - i, @@ -123,13 +128,6 @@ public class KeyguardService extends Service { (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0, info, t, leashMap); - // Use hasAnimatingParent to mark the anything below root task - if (taskId != -1 && change.getParent() != null) { - final TransitionInfo.Change parentChange = info.getChange(change.getParent()); - if (parentChange != null && parentChange.getTaskInfo() != null) { - target.hasAnimatingParent = true; - } - } out.add(target); } return out.toArray(new RemoteAnimationTarget[out.size()]); @@ -173,18 +171,15 @@ public class KeyguardService extends Service { wrap(info, true /* wallpapers */, t, mLeashMap); final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0]; - // Sets the alpha to 0 for the opening root task for fade in animation. And since - // the fade in animation can only apply on the first opening app, so set alpha to 1 - // for anything else. - for (RemoteAnimationTarget target : apps) { - if (target.taskId != -1 - && target.mode == RemoteAnimationTarget.MODE_OPENING - && !target.hasAnimatingParent) { - t.setAlpha(target.leash, 0.0f); - } else { - t.setAlpha(target.leash, 1.0f); + // Set alpha back to 1 for the independent changes because we will be animating + // children instead. + for (TransitionInfo.Change chg : info.getChanges()) { + if (TransitionInfo.isIndependent(chg, info)) { + t.setAlpha(chg.getLeash(), 1.f); } } + initAlphaForAnimationTargets(t, apps); + initAlphaForAnimationTargets(t, wallpapers); t.apply(); synchronized (mFinishCallbacks) { mFinishCallbacks.put(transition, finishCallback); @@ -223,6 +218,14 @@ public class KeyguardService extends Service { // nothing, we'll just let it finish on its own I guess. } } + + private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t, + @NonNull RemoteAnimationTarget[] targets) { + for (RemoteAnimationTarget target : targets) { + if (target.mode != MODE_OPENING) continue; + t.setAlpha(target.leash, 0.f); + } + } }; } @@ -333,15 +336,29 @@ public class KeyguardService extends Service { }; private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() { + private static final String TRACK_NAME = "IKeyguardService"; + + /** + * Helper for tracing the most-recent call on the IKeyguardService interface. + * IKeyguardService is oneway, so we are most interested in the order of the calls as they + * are received. We use an async track to make it easier to visualize in the trace. + * @param name name of the trace section + */ + private static void trace(String name) { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, name, 0); + } @Override // Binder interface public void addStateMonitorCallback(IKeyguardStateCallback callback) { + trace("addStateMonitorCallback"); checkPermission(); mKeyguardViewMediator.addStateMonitorCallback(callback); } @Override // Binder interface public void verifyUnlock(IKeyguardExitCallback callback) { + trace("verifyUnlock"); Trace.beginSection("KeyguardService.mBinder#verifyUnlock"); checkPermission(); mKeyguardViewMediator.verifyUnlock(callback); @@ -350,6 +367,7 @@ public class KeyguardService extends Service { @Override // Binder interface public void setOccluded(boolean isOccluded, boolean animate) { + trace("setOccluded isOccluded=" + isOccluded + " animate=" + animate); Log.d(TAG, "setOccluded(" + isOccluded + ")"); Trace.beginSection("KeyguardService.mBinder#setOccluded"); @@ -360,24 +378,28 @@ public class KeyguardService extends Service { @Override // Binder interface public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { + trace("dismiss message=" + message); checkPermission(); mKeyguardViewMediator.dismiss(callback, message); } @Override // Binder interface public void onDreamingStarted() { + trace("onDreamingStarted"); checkPermission(); mKeyguardViewMediator.onDreamingStarted(); } @Override // Binder interface public void onDreamingStopped() { + trace("onDreamingStopped"); checkPermission(); mKeyguardViewMediator.onDreamingStopped(); } @Override // Binder interface public void onStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { + trace("onStartedGoingToSleep pmSleepReason=" + pmSleepReason); checkPermission(); mKeyguardViewMediator.onStartedGoingToSleep( WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason)); @@ -388,6 +410,8 @@ public class KeyguardService extends Service { @Override // Binder interface public void onFinishedGoingToSleep( @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { + trace("onFinishedGoingToSleep pmSleepReason=" + pmSleepReason + + " cameraGestureTriggered=" + cameraGestureTriggered); checkPermission(); mKeyguardViewMediator.onFinishedGoingToSleep( WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason), @@ -399,6 +423,8 @@ public class KeyguardService extends Service { @Override // Binder interface public void onStartedWakingUp( @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { + trace("onStartedWakingUp pmWakeReason=" + pmWakeReason + + " cameraGestureTriggered=" + cameraGestureTriggered); Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp"); checkPermission(); mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); @@ -409,6 +435,7 @@ public class KeyguardService extends Service { @Override // Binder interface public void onFinishedWakingUp() { + trace("onFinishedWakingUp"); Trace.beginSection("KeyguardService.mBinder#onFinishedWakingUp"); checkPermission(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.FINISHED_WAKING_UP); @@ -417,6 +444,7 @@ public class KeyguardService extends Service { @Override // Binder interface public void onScreenTurningOn(IKeyguardDrawnCallback callback) { + trace("onScreenTurningOn"); Trace.beginSection("KeyguardService.mBinder#onScreenTurningOn"); checkPermission(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_ON, @@ -451,6 +479,7 @@ public class KeyguardService extends Service { @Override // Binder interface public void onScreenTurnedOn() { + trace("onScreenTurnedOn"); Trace.beginSection("KeyguardService.mBinder#onScreenTurnedOn"); checkPermission(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_ON); @@ -460,12 +489,14 @@ public class KeyguardService extends Service { @Override // Binder interface public void onScreenTurningOff() { + trace("onScreenTurningOff"); checkPermission(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_OFF); } @Override // Binder interface public void onScreenTurnedOff() { + trace("onScreenTurnedOff"); checkPermission(); mKeyguardViewMediator.onScreenTurnedOff(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_OFF); @@ -474,12 +505,14 @@ public class KeyguardService extends Service { @Override // Binder interface public void setKeyguardEnabled(boolean enabled) { + trace("setKeyguardEnabled enabled" + enabled); checkPermission(); mKeyguardViewMediator.setKeyguardEnabled(enabled); } @Override // Binder interface public void onSystemReady() { + trace("onSystemReady"); Trace.beginSection("KeyguardService.mBinder#onSystemReady"); checkPermission(); mKeyguardViewMediator.onSystemReady(); @@ -488,24 +521,28 @@ public class KeyguardService extends Service { @Override // Binder interface public void doKeyguardTimeout(Bundle options) { + trace("doKeyguardTimeout"); checkPermission(); mKeyguardViewMediator.doKeyguardTimeout(options); } @Override // Binder interface public void setSwitchingUser(boolean switching) { + trace("setSwitchingUser switching=" + switching); checkPermission(); mKeyguardViewMediator.setSwitchingUser(switching); } @Override // Binder interface public void setCurrentUser(int userId) { + trace("setCurrentUser userId=" + userId); checkPermission(); mKeyguardViewMediator.setCurrentUser(userId); } - @Override + @Override // Binder interface public void onBootCompleted() { + trace("onBootCompleted"); checkPermission(); mKeyguardViewMediator.onBootCompleted(); } @@ -515,28 +552,33 @@ public class KeyguardService extends Service { * {@code IRemoteAnimationRunner#onAnimationStart} instead. */ @Deprecated - @Override + @Override // Binder interface public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + trace("startKeyguardExitAnimation startTime=" + startTime + + " fadeoutDuration=" + fadeoutDuration); Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation"); checkPermission(); mKeyguardViewMediator.startKeyguardExitAnimation(startTime, fadeoutDuration); Trace.endSection(); } - @Override + @Override // Binder interface public void onShortPowerPressedGoHome() { + trace("onShortPowerPressedGoHome"); checkPermission(); mKeyguardViewMediator.onShortPowerPressedGoHome(); } - @Override + @Override // Binder interface public void dismissKeyguardToLaunch(Intent intentToLaunch) { + trace("dismissKeyguardToLaunch"); checkPermission(); mKeyguardViewMediator.dismissKeyguardToLaunch(intentToLaunch); } - @Override + @Override // Binder interface public void onSystemKeyPressed(int keycode) { + trace("onSystemKeyPressed keycode=" + keycode); checkPermission(); mKeyguardViewMediator.onSystemKeyPressed(keycode); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index f96f337d5cb2..122e25975837 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -812,8 +812,8 @@ class KeyguardUnlockAnimationController @Inject constructor( // Translate up from the bottom. surfaceBehindMatrix.setTranslate( - surfaceBehindRemoteAnimationTarget.localBounds.left.toFloat(), - surfaceBehindRemoteAnimationTarget.localBounds.top.toFloat() + + surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(), + surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt index d8affa4d6c21..1978b3d048b7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt @@ -139,7 +139,8 @@ constructor( if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) { Trace.beginSection("ResourceTrimmer#trimMemory") Log.d(LOG_TAG, "SysUI asleep, trimming memory.") - globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) + globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_ALL) Trace.endSection() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt new file mode 100644 index 000000000000..641e20b4a3fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +@SysUISingleton +class KeyguardClockRepository +@Inject +constructor( + private val secureSettings: SecureSettings, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + + val selectedClockSize: Flow<SettingsClockSize> = + secureSettings + .observerFlow( + names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK), + userId = UserHandle.USER_SYSTEM, + ) + .onStart { emit(Unit) } // Forces an initial update. + .map { getClockSize() } + + private suspend fun getClockSize(): SettingsClockSize { + return withContext(backgroundDispatcher) { + if ( + secureSettings.getIntForUser( + Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, + 1, + UserHandle.USER_CURRENT + ) == 1 + ) { + SettingsClockSize.DYNAMIC + } else { + SettingsClockSize.SMALL + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt new file mode 100644 index 000000000000..98f445c4419a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -0,0 +1,34 @@ +/* + * 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 com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardClockRepository +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Encapsulates business-logic related to the keyguard clock. */ +@SysUISingleton +class KeyguardClockInteractor +@Inject +constructor( + repository: KeyguardClockRepository, +) { + val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt new file mode 100644 index 000000000000..c6b0f58a0cd3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt @@ -0,0 +1,23 @@ +/* + * 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 + +enum class SettingsClockSize { + DYNAMIC, + SMALL, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt new file mode 100644 index 000000000000..57c32b3a56d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt @@ -0,0 +1,60 @@ +/* + * 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.ui.binder + +import android.view.View +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.flow.collect + +/** Binder for the small clock view, large clock view and smartspace. */ +object KeyguardPreviewClockSmartspaceViewBinder { + + @JvmStatic + fun bind( + largeClockHostView: View, + smallClockHostView: View, + smartspace: View?, + viewModel: KeyguardPreviewClockSmartspaceViewModel, + ) { + largeClockHostView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it } + } + } + + smallClockHostView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it } + } + } + + smartspace?.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) } + } + } + } + + private fun View.setTopPadding(padding: Int) { + setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 555a09baa5b7..4308d843c27a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -22,6 +22,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.res.Resources import android.graphics.Rect import android.hardware.display.DisplayManager import android.os.Bundle @@ -33,6 +34,7 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout +import androidx.core.view.isInvisible import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.R @@ -40,7 +42,10 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +import com.android.systemui.plugins.ClockController import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.clocks.DefaultClockController import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants @@ -60,6 +65,7 @@ constructor( @Application private val context: Context, @Main private val mainDispatcher: CoroutineDispatcher, @Main private val mainHandler: Handler, + private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel, private val bottomAreaViewModel: KeyguardBottomAreaViewModel, displayManager: DisplayManager, private val windowManager: WindowManager, @@ -79,6 +85,7 @@ constructor( KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, false, ) + /** [shouldHideClock] here means that we never create and bind the clock views */ private val shouldHideClock: Boolean = bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) @@ -87,7 +94,8 @@ constructor( val surfacePackage: SurfaceControlViewHost.SurfacePackage get() = host.surfacePackage - private var clockView: View? = null + private lateinit var largeClockHostView: FrameLayout + private lateinit var smallClockHostView: FrameLayout private var smartSpaceView: View? = null private var colorOverride: Int? = null @@ -126,6 +134,12 @@ constructor( if (!shouldHideClock) { setUpClock(rootView) + KeyguardPreviewClockSmartspaceViewBinder.bind( + largeClockHostView, + smallClockHostView, + smartSpaceView, + clockSmartspaceViewModel, + ) } rootView.measure( @@ -205,11 +219,9 @@ constructor( smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView) val topPadding: Int = - with(context.resources) { - getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) + - getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) + - getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) - } + KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding( + context.resources + ) val startPadding: Int = with(context.resources) { @@ -284,10 +296,19 @@ constructor( } private fun setUpClock(parentView: ViewGroup) { + largeClockHostView = createLargeClockHostView() + largeClockHostView.isInvisible = true + parentView.addView(largeClockHostView) + + smallClockHostView = createSmallClockHostView(parentView.resources) + smallClockHostView.isInvisible = true + parentView.addView(smallClockHostView) + + // TODO (b/283465254): Move the listeners to KeyguardClockRepository val clockChangeListener = object : ClockRegistry.ClockChangeListener { override fun onCurrentClockChanged() { - onClockChanged(parentView) + onClockChanged() } } clockRegistry.registerClockChangeListener(clockChangeListener) @@ -317,62 +338,89 @@ constructor( disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) }) val layoutChangeListener = - object : View.OnLayoutChangeListener { - override fun onLayoutChange( - v: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - if (clockController.clock !is DefaultClockController) { - clockController.clock - ?.largeClock - ?.events - ?.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(parentView) - ) - } + View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + if (clockController.clock !is DefaultClockController) { + clockController.clock + ?.largeClock + ?.events + ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView)) } } - parentView.addOnLayoutChangeListener(layoutChangeListener) - disposables.add( DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) } ) - onClockChanged(parentView) + onClockChanged() + } + + private fun createLargeClockHostView(): FrameLayout { + val hostView = FrameLayout(context) + hostView.layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + ) + return hostView + } + + private fun createSmallClockHostView(resources: Resources): FrameLayout { + val hostView = FrameLayout(context) + val layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + resources.getDimensionPixelSize(R.dimen.small_clock_height) + ) + layoutParams.topMargin = + KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) + + resources.getDimensionPixelSize(R.dimen.small_clock_padding_top) + hostView.layoutParams = layoutParams + + hostView.setPaddingRelative( + resources.getDimensionPixelSize(R.dimen.clock_padding_start), + 0, + 0, + 0 + ) + hostView.clipChildren = false + return hostView } - private fun onClockChanged(parentView: ViewGroup) { + private fun onClockChanged() { val clock = clockRegistry.createCurrentClock() clockController.clock = clock colorOverride?.let { clock.events.onSeedColorChanged(it) } - clock.largeClock.events.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(parentView) - ) - - clockView?.let { parentView.removeView(it) } - clockView = - clock.largeClock.view.apply { - if (shouldHighlightSelectedAffordance) { - alpha = DIM_ALPHA - } - parentView.addView(this) - visibility = View.VISIBLE - } + updateLargeClock(clock) + updateSmallClock(clock) // Hide smart space if the clock has weather display; otherwise show it hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay) } + private fun updateLargeClock(clock: ClockController) { + clock.largeClock.events.onTargetRegionChanged( + KeyguardClockSwitch.getLargeClockRegion(largeClockHostView) + ) + if (shouldHighlightSelectedAffordance) { + clock.largeClock.view.alpha = DIM_ALPHA + } + largeClockHostView.removeAllViews() + largeClockHostView.addView(clock.largeClock.view) + } + + private fun updateSmallClock(clock: ClockController) { + clock.smallClock.events.onTargetRegionChanged( + KeyguardClockSwitch.getSmallClockRegion(smallClockHostView) + ) + if (shouldHighlightSelectedAffordance) { + clock.smallClock.view.alpha = DIM_ALPHA + } + smallClockHostView.removeAllViews() + smallClockHostView.addView(clock.smallClock.view) + } + companion object { private const val KEY_HOST_TOKEN = "host_token" private const val KEY_VIEW_WIDTH = "width" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt new file mode 100644 index 000000000000..00c603b04ccf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt @@ -0,0 +1,77 @@ +/* + * 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.ui.viewmodel + +import android.content.Context +import android.content.res.Resources +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** View model for the small clock view, large clock view and smartspace. */ +class KeyguardPreviewClockSmartspaceViewModel +@Inject +constructor( + @Application private val context: Context, + interactor: KeyguardClockInteractor, +) { + + val isLargeClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC } + + val isSmallClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.SMALL } + + val smartSpaceTopPadding: Flow<Int> = + interactor.selectedClockSize.map { + when (it) { + SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources) + SettingsClockSize.SMALL -> getSmallClockSmartspaceTopPadding(context.resources) + } + } + + companion object { + fun getLargeClockSmartspaceTopPadding(resources: Resources): Int { + return with(resources) { + getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) + + getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) + + getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + } + } + + fun getSmallClockSmartspaceTopPadding(resources: Resources): Int { + return with(resources) { + getStatusBarHeight(this) + + getDimensionPixelSize(R.dimen.small_clock_padding_top) + + getDimensionPixelSize(R.dimen.small_clock_height) + } + } + + fun getStatusBarHeight(resource: Resources): Int { + var result = 0 + val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android") + if (resourceId > 0) { + result = resource.getDimensionPixelSize(resourceId) + } + return result + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 1469d96dd3e8..bce334610f28 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -16,10 +16,12 @@ package com.android.systemui.media.controls.pipeline +import android.annotation.SuppressLint import android.app.BroadcastOptions import android.app.Notification import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME import android.app.PendingIntent +import android.app.StatusBarManager import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession @@ -43,7 +45,6 @@ import android.media.session.PlaybackState import android.net.Uri import android.os.Parcelable import android.os.Process -import android.os.RemoteException import android.os.UserHandle import android.provider.Settings import android.service.notification.StatusBarNotification @@ -53,7 +54,6 @@ import android.util.Log import android.util.Pair as APair import androidx.media.utils.MediaConstants import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable import com.android.systemui.R @@ -185,7 +185,6 @@ class MediaDataManager( private val logger: MediaUiEventLogger, private val smartspaceManager: SmartspaceManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val statusBarService: IStatusBarService, ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -230,6 +229,10 @@ class MediaDataManager( private val artworkHeight = context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded) + @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE + private val statusBarManager = + context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager + /** Check whether this notification is an RCN */ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) @@ -257,7 +260,6 @@ class MediaDataManager( mediaFlags: MediaFlags, logger: MediaUiEventLogger, smartspaceManager: SmartspaceManager, - statusBarService: IStatusBarService, keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, @@ -283,7 +285,6 @@ class MediaDataManager( logger, smartspaceManager, keyguardUpdateMonitor, - statusBarService, ) private val appChangeReceiver = @@ -793,27 +794,12 @@ class MediaDataManager( song = HybridGroupManager.resolveTitle(notif) } if (song.isNullOrBlank()) { - if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) { - // App is required to provide a title: cancel the underlying notification - try { - statusBarService.onNotificationError( - sbn.packageName, - sbn.tag, - sbn.id, - sbn.uid, - sbn.initialPid, - MEDIA_TITLE_ERROR_MESSAGE, - sbn.user.identifier - ) - } catch (e: RemoteException) { - Log.e(TAG, "cancelNotification failed: $e") - } - // Only add log for media removed if active media is updated with invalid title. - foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) } - return - } else { - // For apps that don't have the title requirement yet, add a placeholder - song = context.getString(R.string.controls_media_empty_title, appName) + // For apps that don't include a title, log and add a placeholder + song = context.getString(R.string.controls_media_empty_title, appName) + try { + statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier) + } catch (e: RuntimeException) { + Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}") } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 3751c60b06d5..9bc66f6c98d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -64,9 +64,4 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) - - /** Check whether app is required to provide a non-empty media title */ - fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean { - return StatusBarManager.isMediaTitleRequiredForApp(packageName, user) - } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index bbc86c839ad2..cfb581b67757 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -39,6 +39,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; +import android.icu.text.SimpleDateFormat; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; @@ -101,7 +102,9 @@ import com.android.wm.shell.pip.Pip; import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.Executor; @@ -287,6 +290,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS); private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); + private SimpleDateFormat mLogDateFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US); + private Date mTmpLogDate = new Date(); private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; @@ -1036,11 +1041,17 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } // For debugging purposes, only log edge points + long curTime = System.currentTimeMillis(); + mTmpLogDate.setTime(curTime); + String curTimeStr = mLogDateFormat.format(mTmpLogDate); (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format( - "Gesture [%d,alw=%B,%B,%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]", - System.currentTimeMillis(), isTrackpadMultiFingerSwipe, mAllowGesture, + "Gesture [%d [%s],alw=%B, mltf=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B," + + " qsDisbld=%b, blkdAct=%B, pip=%B," + + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]", + curTime, curTimeStr, mAllowGesture, isTrackpadMultiFingerSwipe, mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed, - QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize, + QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep, + mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 7627c0c22d1e..efbec29bcff1 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -288,15 +288,55 @@ constructor( } /** + * Like [updateNoteTaskAsUser] but automatically apply to the current user and all its work + * profiles. + * + * @see updateNoteTaskAsUser + * @see UserTracker.userHandle + * @see UserTracker.userProfiles + */ + fun updateNoteTaskForCurrentUserAndManagedProfiles() { + updateNoteTaskAsUser(userTracker.userHandle) + for (profile in userTracker.userProfiles) { + if (userManager.isManagedProfile(profile.id)) { + updateNoteTaskAsUser(profile.userHandle) + } + } + } + + /** * Updates all [NoteTaskController] related information, including but not exclusively the * widget shortcut created by the [user] - by default it will use the current user. * + * If the user is not current user, the update will be dispatched to run in that user's process. + * * Keep in mind the shortcut API has a * [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting) * and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the * function during System UI initialization. */ fun updateNoteTaskAsUser(user: UserHandle) { + if (!userManager.isUserUnlocked(user)) { + debugLog { "updateNoteTaskAsUser call but user locked: user=$user" } + return + } + + if (user == userTracker.userHandle) { + updateNoteTaskAsUserInternal(user) + } else { + // TODO(b/278729185): Replace fire and forget service with a bounded service. + val intent = NoteTaskControllerUpdateService.createIntent(context) + context.startServiceAsUser(intent, user) + } + } + + @InternalNoteTaskApi + fun updateNoteTaskAsUserInternal(user: UserHandle) { + if (!userManager.isUserUnlocked(user)) { + debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" } + return + } + val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user) val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty() @@ -314,18 +354,8 @@ constructor( /** @see OnRoleHoldersChangedListener */ fun onRoleHoldersChanged(roleName: String, user: UserHandle) { if (roleName != ROLE_NOTES) return - if (!userManager.isUserUnlocked(user)) { - debugLog { "onRoleHoldersChanged call but user locked: role=$roleName, user=$user" } - return - } - if (user == userTracker.userHandle) { - updateNoteTaskAsUser(user) - } else { - // TODO(b/278729185): Replace fire and forget service with a bounded service. - val intent = NoteTaskControllerUpdateService.createIntent(context) - context.startServiceAsUser(intent, user) - } + updateNoteTaskAsUser(user) } private val SecureSettings.preferredUser: UserHandle diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt index 26b35cc8ffd1..3e352afe3832 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt @@ -44,7 +44,7 @@ constructor( override fun onCreate() { super.onCreate() // TODO(b/278729185): Replace fire and forget service with a bounded service. - controller.updateNoteTaskAsUser(user) + controller.updateNoteTaskAsUserInternal(user) stopSelf() } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 221ff65e4dfe..fe1034a6aa32 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -15,7 +15,10 @@ */ package com.android.systemui.notetask +import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager +import android.content.Context +import android.content.pm.UserInfo import android.os.UserHandle import android.view.KeyEvent import android.view.KeyEvent.KEYCODE_N @@ -54,6 +57,7 @@ constructor( initializeHandleSystemKey() initializeOnRoleHoldersChanged() initializeOnUserUnlocked() + initializeUserTracker() } /** @@ -79,7 +83,7 @@ constructor( private fun initializeOnRoleHoldersChanged() { roleManager.addOnRoleHoldersChangedListenerAsUser( backgroundExecutor, - controller::onRoleHoldersChanged, + callbacks, UserHandle.ALL, ) } @@ -93,18 +97,41 @@ constructor( */ private fun initializeOnUserUnlocked() { if (keyguardUpdateMonitor.isUserUnlocked(userTracker.userId)) { - controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle) - } else { - keyguardUpdateMonitor.registerCallback(onUserUnlockedCallback) + controller.updateNoteTaskForCurrentUserAndManagedProfiles() } + keyguardUpdateMonitor.registerCallback(callbacks) } - // KeyguardUpdateMonitor.registerCallback uses a weak reference, so we need a hard reference. - private val onUserUnlockedCallback = - object : KeyguardUpdateMonitorCallback() { + private fun initializeUserTracker() { + userTracker.addCallback(callbacks, backgroundExecutor) + } + + // Some callbacks use a weak reference, so we play safe and keep a hard reference to them all. + private val callbacks = + object : + KeyguardUpdateMonitorCallback(), + CommandQueue.Callbacks, + UserTracker.Callback, + OnRoleHoldersChangedListener { + + override fun handleSystemKey(key: KeyEvent) { + key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask) + } + + override fun onRoleHoldersChanged(roleName: String, user: UserHandle) { + controller.onRoleHoldersChanged(roleName, user) + } + override fun onUserUnlocked() { - controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle) - keyguardUpdateMonitor.removeCallback(this) + controller.updateNoteTaskForCurrentUserAndManagedProfiles() + } + + override fun onUserChanged(newUser: Int, userContext: Context) { + controller.updateNoteTaskForCurrentUserAndManagedProfiles() + } + + override fun onProfilesChanged(profiles: List<UserInfo>) { + controller.updateNoteTaskForCurrentUserAndManagedProfiles() } } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 6d7455f2b259..752471d83735 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -16,12 +16,16 @@ package com.android.systemui.scene +import com.android.systemui.scene.data.model.SceneContainerConfigModule import com.android.systemui.scene.ui.composable.SceneModule +import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule import dagger.Module @Module( includes = [ + SceneContainerConfigModule::class, + SceneContainerViewModelModule::class, SceneModule::class, ], ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt new file mode 100644 index 000000000000..0af80949f95e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt @@ -0,0 +1,61 @@ +/* + * 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.scene.data.model + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.shared.model.SceneContainerNames +import com.android.systemui.scene.shared.model.SceneKey +import dagger.Module +import dagger.Provides +import javax.inject.Named + +@Module +object SceneContainerConfigModule { + + @Provides + fun containerConfigs(): Map<String, SceneContainerConfig> { + return mapOf( + SceneContainerNames.SYSTEM_UI_DEFAULT to + SceneContainerConfig( + name = SceneContainerNames.SYSTEM_UI_DEFAULT, + // Note that this list is in z-order. The first one is the bottom-most and the + // last + // one is top-most. + sceneKeys = + listOf( + SceneKey.Gone, + SceneKey.Lockscreen, + SceneKey.Bouncer, + SceneKey.Shade, + SceneKey.QuickSettings, + ), + initialSceneKey = SceneKey.Lockscreen, + ), + ) + } + + @Provides + @SysUISingleton + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + fun provideDefaultSceneContainerConfig( + configs: Map<String, SceneContainerConfig>, + ): SceneContainerConfig { + return checkNotNull(configs[SceneContainerNames.SYSTEM_UI_DEFAULT]) { + "No SceneContainerConfig named \"${SceneContainerNames.SYSTEM_UI_DEFAULT}\"." + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt new file mode 100644 index 000000000000..64f5087d99bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt @@ -0,0 +1,21 @@ +/* + * 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.scene.shared.model + +object SceneContainerNames { + const val SYSTEM_UI_DEFAULT = "system_ui" +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index a4daafccd75b..8c1ad9b4571b 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -19,17 +19,12 @@ package com.android.systemui.scene.ui.viewmodel import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.StateFlow /** Models UI state for a single scene container. */ -class SceneContainerViewModel -@AssistedInject -constructor( +class SceneContainerViewModel( private val interactor: SceneInteractor, - @Assisted val containerName: String, + val containerName: String, ) { /** * Keys of all scenes in the container. @@ -54,11 +49,4 @@ constructor( fun setSceneTransitionProgress(progress: Float) { interactor.setSceneTransitionProgress(containerName, progress) } - - @AssistedFactory - interface Factory { - fun create( - containerName: String, - ): SceneContainerViewModel - } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt new file mode 100644 index 000000000000..100f42764322 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt @@ -0,0 +1,40 @@ +/* + * 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.scene.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.SceneContainerNames +import dagger.Module +import dagger.Provides +import javax.inject.Named + +@Module +object SceneContainerViewModelModule { + + @Provides + @SysUISingleton + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + fun defaultSceneContainerViewModel( + interactor: SceneInteractor, + ): SceneContainerViewModel { + return SceneContainerViewModel( + interactor = interactor, + containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index fb4feb8c64b4..a532195c5b9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -33,7 +33,6 @@ import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.Icon; import android.hardware.biometrics.BiometricAuthenticator.Modality; -import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; @@ -317,7 +316,7 @@ public class CommandQueue extends IStatusBar.Stub implements IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId, long operationId, String opPackageName, - long requestId, @BiometricMultiSensorMode int multiSensorConfig) { + long requestId) { } /** @see IStatusBar#onBiometricAuthenticated(int) */ @@ -956,8 +955,7 @@ public class CommandQueue extends IStatusBar.Stub implements @Override public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, - int userId, long operationId, String opPackageName, long requestId, - @BiometricMultiSensorMode int multiSensorConfig) { + int userId, long operationId, String opPackageName, long requestId) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = promptInfo; @@ -969,7 +967,6 @@ public class CommandQueue extends IStatusBar.Stub implements args.arg6 = opPackageName; args.argl1 = operationId; args.argl2 = requestId; - args.argi2 = multiSensorConfig; mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args) .sendToTarget(); } @@ -1573,8 +1570,7 @@ public class CommandQueue extends IStatusBar.Stub implements someArgs.argi1 /* userId */, someArgs.argl1 /* operationId */, (String) someArgs.arg6 /* opPackageName */, - someArgs.argl2 /* requestId */, - someArgs.argi2 /* multiSensorConfig */); + someArgs.argl2 /* requestId */); } someArgs.recycle(); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index 993c3801cecd..b956207190b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -354,7 +354,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc @Override protected void snapChild(final View animView, final float targetLeft, float velocity) { - superSnapChild(animView, targetLeft, velocity); + if (animView instanceof SwipeableView) { + // only perform the snapback animation on views that are swipeable inside the shade. + superSnapChild(animView, targetLeft, velocity); + } + mCallback.onDragCancelled(animView); if (targetLeft == 0) { handleMenuCoveredOrDismissed(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index a6b2bd89bfa5..f26a84ba20a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -30,6 +30,8 @@ import android.view.InsetsFlags; import android.view.ViewDebug; import android.view.WindowInsetsController.Appearance; +import androidx.annotation.NonNull; + import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.view.AppearanceRegion; import com.android.systemui.Dumpable; @@ -46,7 +48,6 @@ import com.android.systemui.util.Compile; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Date; import javax.inject.Inject; @@ -57,7 +58,8 @@ import javax.inject.Inject; public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable { private static final String TAG = "LightBarController"; - private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG; + private static final boolean DEBUG_LOGS = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f; @@ -113,6 +115,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private String mLastSetScrimStateLog; private String mLastNavigationBarAppearanceChangedLog; + private StringBuilder mLogStringBuilder = null; @Inject public LightBarController( @@ -193,35 +196,43 @@ public class LightBarController implements BatteryController.BatteryStateChangeC final boolean darkForTop = darkForQs || mGlobalActionsVisible; mNavigationLight = ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop; - mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()" - + " appearance=" + appearance - + " nbModeChanged=" + nbModeChanged - + " navigationBarMode=" + navigationBarMode - + " navbarColorManagedByIme=" + navbarColorManagedByIme - + " mHasLightNavigationBar=" + mHasLightNavigationBar - + " ignoreScrimForce=" + ignoreScrimForce - + " darkForScrim=" + darkForScrim - + " lightForScrim=" + lightForScrim - + " darkForQs=" + darkForQs - + " darkForTop=" + darkForTop - + " mNavigationLight=" + mNavigationLight - + " last=" + last - + " timestamp=" + new Date(); - if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog); + if (DEBUG_NAVBAR) { + mLastNavigationBarAppearanceChangedLog = getLogStringBuilder() + .append("onNavigationBarAppearanceChanged()") + .append(" appearance=").append(appearance) + .append(" nbModeChanged=").append(nbModeChanged) + .append(" navigationBarMode=").append(navigationBarMode) + .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme) + .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar) + .append(" ignoreScrimForce=").append(ignoreScrimForce) + .append(" darkForScrim=").append(darkForScrim) + .append(" lightForScrim=").append(lightForScrim) + .append(" darkForQs=").append(darkForQs) + .append(" darkForTop=").append(darkForTop) + .append(" mNavigationLight=").append(mNavigationLight) + .append(" last=").append(last) + .append(" timestamp=").append(System.currentTimeMillis()) + .toString(); + if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog); + } } else { mNavigationLight = mHasLightNavigationBar && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim) && !mQsCustomizing; - mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()" - + " appearance=" + appearance - + " nbModeChanged=" + nbModeChanged - + " navigationBarMode=" + navigationBarMode - + " navbarColorManagedByIme=" + navbarColorManagedByIme - + " mHasLightNavigationBar=" + mHasLightNavigationBar - + " mNavigationLight=" + mNavigationLight - + " last=" + last - + " timestamp=" + new Date(); - if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog); + if (DEBUG_NAVBAR) { + mLastNavigationBarAppearanceChangedLog = getLogStringBuilder() + .append("onNavigationBarAppearanceChanged()") + .append(" appearance=").append(appearance) + .append(" nbModeChanged=").append(nbModeChanged) + .append(" navigationBarMode=").append(navigationBarMode) + .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme) + .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar) + .append(" mNavigationLight=").append(mNavigationLight) + .append(" last=").append(last) + .append(" timestamp=").append(System.currentTimeMillis()) + .toString(); + if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog); + } } if (mNavigationLight != last) { updateNavigation(); @@ -319,18 +330,22 @@ public class LightBarController implements BatteryController.BatteryStateChangeC } else { if (mForceLightForScrim != forceLightForScrimLast) reevaluate(); } - mLastSetScrimStateLog = "setScrimState()" - + " scrimState=" + scrimState - + " scrimBehindAlpha=" + scrimBehindAlpha - + " scrimInFrontColor=" + scrimInFrontColor - + " forceForScrim=" + forceForScrim - + " scrimColorIsLight=" + scrimColorIsLight - + " mHasLightNavigationBar=" + mHasLightNavigationBar - + " mBouncerVisible=" + mBouncerVisible - + " mForceDarkForScrim=" + mForceDarkForScrim - + " mForceLightForScrim=" + mForceLightForScrim - + " timestamp=" + new Date(); - if (DEBUG) Log.d(TAG, mLastSetScrimStateLog); + if (DEBUG_NAVBAR) { + mLastSetScrimStateLog = getLogStringBuilder() + .append("setScrimState()") + .append(" scrimState=").append(scrimState) + .append(" scrimBehindAlpha=").append(scrimBehindAlpha) + .append(" scrimInFrontColor=").append(scrimInFrontColor) + .append(" forceForScrim=").append(forceForScrim) + .append(" scrimColorIsLight=").append(scrimColorIsLight) + .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar) + .append(" mBouncerVisible=").append(mBouncerVisible) + .append(" mForceDarkForScrim=").append(mForceDarkForScrim) + .append(" mForceLightForScrim=").append(mForceLightForScrim) + .append(" timestamp=").append(System.currentTimeMillis()) + .toString(); + if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog); + } } else { boolean forceDarkForScrimLast = mForceDarkForScrim; // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold. @@ -344,15 +359,28 @@ public class LightBarController implements BatteryController.BatteryStateChangeC if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) { reevaluate(); } - mLastSetScrimStateLog = "setScrimState()" - + " scrimState=" + scrimState - + " scrimBehindAlpha=" + scrimBehindAlpha - + " scrimInFrontColor=" + scrimInFrontColor - + " mHasLightNavigationBar=" + mHasLightNavigationBar - + " mForceDarkForScrim=" + mForceDarkForScrim - + " timestamp=" + new Date(); - if (DEBUG) Log.d(TAG, mLastSetScrimStateLog); + if (DEBUG_NAVBAR) { + mLastSetScrimStateLog = getLogStringBuilder() + .append("setScrimState()") + .append(" scrimState=").append(scrimState) + .append(" scrimBehindAlpha=").append(scrimBehindAlpha) + .append(" scrimInFrontColor=").append(scrimInFrontColor) + .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar) + .append(" mForceDarkForScrim=").append(mForceDarkForScrim) + .append(" timestamp=").append(System.currentTimeMillis()) + .toString(); + if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog); + } + } + } + + @NonNull + private StringBuilder getLogStringBuilder() { + if (mLogStringBuilder == null) { + mLogStringBuilder = new StringBuilder(); } + mLogStringBuilder.setLength(0); + return mLogStringBuilder; } private static boolean isLight(int appearance, int barMode, int flag) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 5ba02fa20167..b24a69292186 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -73,7 +73,6 @@ import android.os.Message; import android.os.SystemClock; import android.os.Trace; import android.os.VibrationEffect; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.text.InputFilter; @@ -113,7 +112,6 @@ import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.graphics.drawable.BackgroundBlurDrawable; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.view.RotationPolicy; @@ -133,15 +131,11 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; -import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.RoundedCornerProgressDrawable; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -198,9 +192,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private ViewGroup mDialogRowsView; private ViewGroup mRinger; - private DeviceConfigProxy mDeviceConfigProxy; - private Executor mExecutor; - /** * Container for the top part of the dialog, which contains the ringer, the ringer drawer, the * volume rows, and the ellipsis button. This does not include the live caption button. @@ -290,14 +281,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private BackgroundBlurDrawable mDialogRowsViewBackground; private final InteractionJankMonitor mInteractionJankMonitor; - private boolean mSeparateNotification; - private int mWindowGravity; @VisibleForTesting - int mVolumeRingerIconDrawableId; + final int mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on; @VisibleForTesting - int mVolumeRingerMuteIconDrawableId; + final int mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute; private int mOriginalGravity; private final DevicePostureController.Callback mDevicePostureControllerCallback; @@ -315,8 +304,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, VolumePanelFactory volumePanelFactory, ActivityStarter activityStarter, InteractionJankMonitor interactionJankMonitor, - DeviceConfigProxy deviceConfigProxy, - Executor executor, CsdWarningDialog.Factory csdWarningDialogFactory, DevicePostureController devicePostureController, Looper looper, @@ -374,12 +361,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } else { mDevicePostureControllerCallback = null; } - - mDeviceConfigProxy = deviceConfigProxy; - mExecutor = executor; - mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); - updateRingerModeIconSet(); } /** @@ -401,44 +382,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, return mWindowGravity; } - /** - * If ringer and notification are the same stream (T and earlier), use notification-like bell - * icon set. - * If ringer and notification are separated, then use generic speaker icons. - */ - private void updateRingerModeIconSet() { - if (mSeparateNotification) { - mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on; - mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute; - } else { - mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer; - mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute; - } - - if (mRingerDrawerMuteIcon != null) { - mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId); - } - if (mRingerDrawerNormalIcon != null) { - mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId); - } - } - - /** - * Change icon for ring stream (not ringer mode icon) - */ - private void updateRingRowIcon() { - Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING) - .findFirst(); - if (volumeRow.isPresent()) { - VolumeRow volRow = volumeRow.get(); - volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume - : R.drawable.ic_volume_ringer; - volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off - : R.drawable.ic_volume_ringer_mute; - volRow.setIcon(volRow.iconRes, mContext.getTheme()); - } - } - @Override public void onUiModeChanged() { mContext.getTheme().applyStyle(mContext.getThemeResId(), true); @@ -454,9 +397,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mConfigurationController.addCallback(this); - mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mExecutor, this::onDeviceConfigChange); - if (mDevicePostureController != null) { mDevicePostureController.addCallback(mDevicePostureControllerCallback); } @@ -464,31 +404,15 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @Override public void destroy() { + Log.d(TAG, "destroy() called"); mController.removeCallback(mControllerCallbackH); mHandler.removeCallbacksAndMessages(null); mConfigurationController.removeCallback(this); - mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange); if (mDevicePostureController != null) { mDevicePostureController.removeCallback(mDevicePostureControllerCallback); } } - /** - * Update ringer mode icon based on the config - */ - private void onDeviceConfigChange(DeviceConfig.Properties properties) { - Set<String> changeSet = properties.getKeyset(); - if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) { - boolean newVal = properties.getBoolean( - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); - if (newVal != mSeparateNotification) { - mSeparateNotification = newVal; - updateRingerModeIconSet(); - updateRingRowIcon(); - } - } - } - @Override public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) { // Set touchable region insets on the root dialog view. This tells WindowManager that @@ -542,6 +466,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } private void initDialog(int lockTaskModeState) { + Log.d(TAG, "initDialog: called!"); mDialog = new CustomDialog(mContext); initDimens(); @@ -699,7 +624,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); - updateRingerModeIconSet(); + if (mRingerDrawerMuteIcon != null) { + mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId); + } + if (mRingerDrawerNormalIcon != null) { + mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId); + } setupRingerDrawer(); @@ -724,13 +654,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, addRow(AudioManager.STREAM_MUSIC, R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true); if (!AudioSystem.isSingleVolume(mContext)) { - if (mSeparateNotification) { - addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume, - R.drawable.ic_ring_volume_off, true, false); - } else { - addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer, - R.drawable.ic_volume_ringer, true, false); - } + + addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume, + R.drawable.ic_ring_volume_off, true, false); + addRow(STREAM_ALARM, R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false); @@ -1344,7 +1271,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } protected void tryToRemoveCaptionsTooltip() { - if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { + if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null && mDialog != null) { ViewGroup container = mDialog.findViewById(R.id.volume_dialog_container); container.removeView(mODICaptionsTooltipView); mODICaptionsTooltipView = null; @@ -1551,8 +1478,16 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mHandler.removeMessages(H.DISMISS); mHandler.removeMessages(H.SHOW); - if (mIsAnimatingDismiss) { - Log.d(TAG, "dismissH: isAnimatingDismiss"); + + boolean showingStateInconsistent = !mShowing && mDialog != null && mDialog.isShowing(); + // If incorrectly assuming dialog is not showing, continue and make the state consistent. + if (showingStateInconsistent) { + Log.d(TAG, "dismissH: volume dialog possible in inconsistent state:" + + "mShowing=" + mShowing + ", mDialog==null?" + (mDialog == null)); + } + if (mIsAnimatingDismiss && !showingStateInconsistent) { + Log.d(TAG, "dismissH: skipping dismiss because isAnimatingDismiss is true" + + " and showingStateInconsistent is false"); Trace.endSection(); return; } @@ -1570,8 +1505,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .setDuration(mDialogHideAnimationDurationMs) .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) .withEndAction(() -> mHandler.postDelayed(() -> { - mController.notifyVisible(false); - mDialog.dismiss(); + if (mController != null) { + mController.notifyVisible(false); + } + if (mDialog != null) { + mDialog.dismiss(); + } tryToRemoveCaptionsTooltip(); mIsAnimatingDismiss = false; diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index bb04f82fcffa..aa4ee545a500 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -21,7 +21,6 @@ import android.media.AudioManager; import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; @@ -31,7 +30,6 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.volume.CsdWarningDialog; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; @@ -42,8 +40,6 @@ import dagger.Binds; import dagger.Module; import dagger.Provides; -import java.util.concurrent.Executor; - /** Dagger Module for code in the volume package. */ @Module public interface VolumeModule { @@ -63,8 +59,6 @@ public interface VolumeModule { VolumePanelFactory volumePanelFactory, ActivityStarter activityStarter, InteractionJankMonitor interactionJankMonitor, - DeviceConfigProxy deviceConfigProxy, - @Main Executor executor, CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, DumpManager dumpManager) { @@ -78,8 +72,6 @@ public interface VolumeModule { volumePanelFactory, activityStarter, interactionJankMonitor, - deviceConfigProxy, - executor, csdFactory, devicePostureController, Looper.getMainLooper(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 44c99053eb47..1990c8f644b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -145,50 +145,59 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() = testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) assertThat(isUnlocked).isFalse() assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(isUnlocked).isTrue() + assertThat(failedAttemptCount).isEqualTo(0) } @Test fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) assertThat(isUnlocked).isFalse() assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse() assertThat(isUnlocked).isFalse() + assertThat(failedAttemptCount).isEqualTo(1) } @Test fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() = testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password")) assertThat(isUnlocked).isFalse() assertThat(underTest.authenticate("password".toList())).isTrue() assertThat(isUnlocked).isTrue() + assertThat(failedAttemptCount).isEqualTo(0) } @Test fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password")) assertThat(isUnlocked).isFalse() assertThat(underTest.authenticate("alohomora".toList())).isFalse() assertThat(isUnlocked).isFalse() + assertThat(failedAttemptCount).isEqualTo(1) } @Test fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() = testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) underTest.setAuthenticationMethod( AuthenticationMethodModel.Pattern( @@ -230,11 +239,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) .isTrue() assertThat(isUnlocked).isTrue() + assertThat(failedAttemptCount).isEqualTo(0) } @Test fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) underTest.setAuthenticationMethod( AuthenticationMethodModel.Pattern( @@ -276,6 +287,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) .isFalse() assertThat(isUnlocked).isFalse() + assertThat(failedAttemptCount).isEqualTo(1) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt index 213dc8772cfa..2d1e8a830fc9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt @@ -73,7 +73,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { @Test fun fingerprintSuccessDoesNotRequireExplicitConfirmation() { - biometricView.onDialogAnimatedIn() + biometricView.onDialogAnimatedIn(fingerprintWasStarted = true) biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT) TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() @@ -84,7 +84,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { @Test fun faceSuccessRequiresExplicitConfirmation() { - biometricView.onDialogAnimatedIn() + biometricView.onDialogAnimatedIn(fingerprintWasStarted = true) biometricView.onAuthenticationSucceeded(TYPE_FACE) waitForIdleSync() @@ -104,7 +104,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { @Test fun ignoresFaceErrors_faceIsNotClass3_notLockoutError() { - biometricView.onDialogAnimatedIn() + biometricView.onDialogAnimatedIn(fingerprintWasStarted = true) biometricView.onError(TYPE_FACE, "not a face") waitForIdleSync() @@ -121,7 +121,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { @Test fun doNotIgnoresFaceErrors_faceIsClass3_notLockoutError() { biometricView.isFaceClass3 = true - biometricView.onDialogAnimatedIn() + biometricView.onDialogAnimatedIn(fingerprintWasStarted = true) biometricView.onError(TYPE_FACE, "not a face") waitForIdleSync() @@ -138,7 +138,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { @Test fun doNotIgnoresFaceErrors_faceIsClass3_lockoutError() { biometricView.isFaceClass3 = true - biometricView.onDialogAnimatedIn() + biometricView.onDialogAnimatedIn(fingerprintWasStarted = true) biometricView.onError( TYPE_FACE, FaceManager.getErrorString( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt index 22ebc7ec3c58..8e5d96b0a2c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt @@ -120,7 +120,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { @Test fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() { - biometricView.onDialogAnimatedIn() + biometricView.onDialogAnimatedIn(fingerprintWasStarted = true) biometricView.mNegativeButton.performClick() TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() @@ -212,7 +212,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { @Test fun testIgnoresUselessHelp() { biometricView.mAnimationDurationHideDialog = 10_000 - biometricView.onDialogAnimatedIn() + biometricView.onDialogAnimatedIn(fingerprintWasStarted = true) waitForIdleSync() assertThat(biometricView.isAuthenticating).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 9d68cf3aee01..d31a86ae2809 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -41,11 +41,15 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository -import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor -import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl +import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor +import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel +import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -53,29 +57,34 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import org.junit.After +import org.junit.Before import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest -class AuthContainerViewTest : SysuiTestCase() { +open class AuthContainerViewTest : SysuiTestCase() { @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + private val featureFlags = FakeFeatureFlags() + @Mock lateinit var callback: AuthDialogCallback @Mock @@ -91,16 +100,25 @@ class AuthContainerViewTest : SysuiTestCase() { @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + // TODO(b/278622168): remove with flag + open val useNewBiometricPrompt = false + private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val biometricPromptRepository = FakePromptRepository() private val rearDisplayStateRepository = FakeRearDisplayStateRepository() private val credentialInteractor = FakeCredentialInteractor() - private val bpCredentialInteractor = BiometricPromptCredentialInteractor( + private val bpCredentialInteractor = PromptCredentialInteractor( Dispatchers.Main.immediate, biometricPromptRepository, - credentialInteractor + credentialInteractor, ) + private val promptSelectorInteractor by lazy { + PromptSelectorInteractorImpl( + biometricPromptRepository, + lockPatternUtils, + ) + } private val displayStateInteractor = DisplayStateInteractorImpl( testScope.backgroundScope, mContext, @@ -115,6 +133,11 @@ class AuthContainerViewTest : SysuiTestCase() { private var authContainer: TestAuthContainerView? = null + @Before + fun setup() { + featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt) + } + @After fun tearDown() { if (authContainer?.isAttachedToWindow == true) { @@ -125,7 +148,7 @@ class AuthContainerViewTest : SysuiTestCase() { @Test fun testNotifiesAnimatedIn() { initializeFingerprintContainer() - verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L) + verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */) } @Test @@ -164,13 +187,13 @@ class AuthContainerViewTest : SysuiTestCase() { container.dismissFromSystemServer() waitForIdleSync() - verify(callback, never()).onDialogAnimatedIn(anyLong()) + verify(callback, never()).onDialogAnimatedIn(anyLong(), anyBoolean()) container.addToView() waitForIdleSync() // attaching the view resets the state and allows this to happen again - verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L) + verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */) } @Test @@ -185,7 +208,7 @@ class AuthContainerViewTest : SysuiTestCase() { // the first time is triggered by initializeFingerprintContainer() // the second time was triggered by dismissWithoutCallback() - verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L) + verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */) } @Test @@ -479,6 +502,8 @@ class AuthContainerViewTest : SysuiTestCase() { this.authenticators = authenticators } }, + featureFlags, + testScope.backgroundScope, fingerprintProps, faceProps, wakefulnessLifecycle, @@ -486,8 +511,10 @@ class AuthContainerViewTest : SysuiTestCase() { userManager, lockPatternUtils, interactionJankMonitor, - { bpCredentialInteractor }, { authBiometricFingerprintViewModel }, + { promptSelectorInteractor }, + { bpCredentialInteractor }, + PromptViewModel(promptSelectorInteractor), { credentialViewModel }, Handler(TestableLooper.get(this).looper), fakeExecutor @@ -497,7 +524,10 @@ class AuthContainerViewTest : SysuiTestCase() { } } - override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages() + override fun waitForIdleSync() { + testScope.runCurrent() + TestableLooper.get(this).processAllMessages() + } private fun AuthContainerView.addToView() { ViewUtils.attachView(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt new file mode 100644 index 000000000000..b56d05537215 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt @@ -0,0 +1,30 @@ +/* + * 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.biometrics + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.junit.runner.RunWith + +// TODO(b/278622168): remove with flag +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class AuthContainerViewTest2 : AuthContainerViewTest() { + override val useNewBiometricPrompt = true +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index a326cc7045dd..b9f92a064bc8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -18,7 +18,6 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; -import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; import static com.google.common.truth.Truth.assertThat; @@ -54,7 +53,6 @@ import android.content.res.Resources; import android.graphics.Point; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.ComponentInfoInternal; @@ -91,10 +89,14 @@ import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.udfps.UdfpsUtils; import com.android.systemui.RoboPilotTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; +import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; +import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.VibratorHelper; @@ -171,12 +173,16 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private InteractionJankMonitor mInteractionJankMonitor; @Mock - private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor; + private PromptCredentialInteractor mBiometricPromptCredentialInteractor; + @Mock + private PromptSelectorInteractor mPromptSelectionInteractor; @Mock private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel; @Mock private CredentialViewModel mCredentialViewModel; @Mock + private PromptViewModel mPromptViewModel; + @Mock private UdfpsUtils mUdfpsUtils; @Captor @@ -194,12 +200,17 @@ public class AuthControllerTest extends SysuiTestCase { private Handler mHandler; private DelayableExecutor mBackgroundExecutor; private TestableAuthController mAuthController; + private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private VibratorHelper mVibratorHelper; @Before public void setup() throws RemoteException { + // TODO(b/278622168): remove with flag + // AuthController simply passes this through to AuthContainerView (does not impact test) + mFeatureFlags.set(Flags.BIOMETRIC_BP_STRONG, false); + mContextSpy = spy(mContext); mExecution = new FakeExecution(); mTestableLooper = TestableLooper.get(this); @@ -952,8 +963,7 @@ public class AuthControllerTest extends SysuiTestCase { 0 /* userId */, 0 /* operationId */, "testPackage", - REQUEST_ID, - BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE); + REQUEST_ID); } private void switchTask(String packageName) { @@ -993,25 +1003,26 @@ public class AuthControllerTest extends SysuiTestCase { private PromptInfo mLastBiometricPromptInfo; TestableAuthController(Context context) { - super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, + super(context, mFeatureFlags, null /* applicationCoroutineScope */, + mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, () -> mUdfpsController, () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger, - mLogContextInteractor, () -> mBiometricPromptCredentialInteractor, - () -> mAuthBiometricFingerprintViewModel, () -> mCredentialViewModel, - mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper, - mUdfpsUtils); + mLogContextInteractor, () -> mAuthBiometricFingerprintViewModel, + () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor, + () -> mCredentialViewModel, () -> mPromptViewModel, + mInteractionJankMonitor, mHandler, + mBackgroundExecutor, mVibratorHelper, mUdfpsUtils); } @Override protected AuthDialog buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, - @BiometricManager.BiometricMultiSensorMode int multiSensorConfig, WakefulnessLifecycle wakefulnessLifecycle, AuthDialogPanelInteractionDetector panelInteractionDetector, UserManager userManager, - LockPatternUtils lockPatternUtils) { + LockPatternUtils lockPatternUtils, PromptViewModel viewModel) { mLastBiometricPromptInfo = promptInfo; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt index 1379a0eeebdd..94244cdf271d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt @@ -18,10 +18,11 @@ package com.android.systemui.biometrics import android.annotation.IdRes import android.content.Context -import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.BiometricManager.Authenticators import android.hardware.biometrics.ComponentInfoInternal import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.SensorProperties +import android.hardware.biometrics.SensorPropertiesInternal import android.hardware.face.FaceSensorProperties import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties @@ -61,9 +62,9 @@ internal fun <T : AuthBiometricView> Int.asTestAuthBiometricView( private fun buildPromptInfo(allowDeviceCredential: Boolean): PromptInfo { val promptInfo = PromptInfo() promptInfo.title = "Title" - var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK + var authenticators = Authenticators.BIOMETRIC_WEAK if (allowDeviceCredential) { - authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL + authenticators = authenticators or Authenticators.DEVICE_CREDENTIAL } else { promptInfo.negativeButtonText = "Negative" } @@ -80,7 +81,8 @@ internal fun AuthBiometricView?.destroyDialog() { /** Create [FingerprintSensorPropertiesInternal] for a test. */ internal fun fingerprintSensorPropertiesInternal( - ids: List<Int> = listOf(0) + ids: List<Int> = listOf(0), + strong: Boolean = true, ): List<FingerprintSensorPropertiesInternal> { val componentInfo = listOf( @@ -102,7 +104,7 @@ internal fun fingerprintSensorPropertiesInternal( return ids.map { id -> FingerprintSensorPropertiesInternal( id, - SensorProperties.STRENGTH_STRONG, + if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */, componentInfo, FingerprintSensorProperties.TYPE_REAR, @@ -113,7 +115,8 @@ internal fun fingerprintSensorPropertiesInternal( /** Create [FaceSensorPropertiesInternal] for a test. */ internal fun faceSensorPropertiesInternal( - ids: List<Int> = listOf(1) + ids: List<Int> = listOf(1), + strong: Boolean = true, ): List<FaceSensorPropertiesInternal> { val componentInfo = listOf( @@ -135,7 +138,7 @@ internal fun faceSensorPropertiesInternal( return ids.map { id -> FaceSensorPropertiesInternal( id, - SensorProperties.STRENGTH_STRONG, + if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK, 2 /* maxEnrollmentsPerUser */, componentInfo, FaceSensorProperties.TYPE_RGB, @@ -146,6 +149,24 @@ internal fun faceSensorPropertiesInternal( } } +@Authenticators.Types +internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes(): Int { + var authenticators = Authenticators.EMPTY_SET + mapNotNull { it?.sensorStrength } + .forEach { strength -> + authenticators = + authenticators or + when (strength) { + SensorProperties.STRENGTH_CONVENIENCE -> + Authenticators.BIOMETRIC_CONVENIENCE + SensorProperties.STRENGTH_WEAK -> Authenticators.BIOMETRIC_WEAK + SensorProperties.STRENGTH_STRONG -> Authenticators.BIOMETRIC_STRONG + else -> Authenticators.EMPTY_SET + } + } + return authenticators +} + internal fun promptInfo( title: String = "title", subtitle: String = "sub", diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt index 2d5614c15173..4836af635aed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt @@ -4,7 +4,7 @@ import android.hardware.biometrics.PromptInfo import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController -import com.android.systemui.biometrics.data.model.PromptKind +import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat @@ -60,7 +60,7 @@ class PromptRepositoryImplTest : SysuiTestCase() { @Test fun setsAndUnsetsPrompt() = runBlockingTest { - val kind = PromptKind.PIN + val kind = PromptKind.Pin val uid = 8 val challenge = 90L val promptInfo = PromptInfo() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt index dbcbf415221e..720a35c9024f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt @@ -9,15 +9,17 @@ import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.domain.model.BiometricUserInfo import com.android.systemui.biometrics.promptInfo +import com.android.systemui.coroutines.collectLastValue import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch -import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule @@ -36,42 +38,39 @@ class PromptCredentialInteractorTest : SysuiTestCase() { @JvmField @Rule var mockitoRule = MockitoJUnit.rule() - private val dispatcher = UnconfinedTestDispatcher() + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) private val biometricPromptRepository = FakePromptRepository() private val credentialInteractor = FakeCredentialInteractor() - private lateinit var interactor: BiometricPromptCredentialInteractor + private lateinit var interactor: PromptCredentialInteractor @Before fun setup() { interactor = - BiometricPromptCredentialInteractor( - dispatcher, + PromptCredentialInteractor( + testDispatcher, biometricPromptRepository, - credentialInteractor + credentialInteractor, ) } @Test fun testIsShowing() = - runTest(dispatcher) { - var showing = false - val job = launch { interactor.isShowing.collect { showing = it } } + testScope.runTest { + val showing by collectLastValue(interactor.isShowing) biometricPromptRepository.setIsShowing(false) assertThat(showing).isFalse() biometricPromptRepository.setIsShowing(true) assertThat(showing).isTrue() - - job.cancel() } @Test fun testShowError() = - runTest(dispatcher) { - var error: CredentialStatus.Fail? = null - val job = launch { interactor.verificationError.collect { error = it } } + testScope.runTest { + val error by collectLastValue(interactor.verificationError) for (msg in listOf("once", "again")) { interactor.setVerificationError(error(msg)) @@ -80,19 +79,14 @@ class PromptCredentialInteractorTest : SysuiTestCase() { interactor.resetVerificationError() assertThat(error).isNull() - - job.cancel() } @Test fun nullWhenNoPromptInfo() = - runTest(dispatcher) { - var prompt: BiometricPromptRequest? = null - val job = launch { interactor.prompt.collect { prompt = it } } + testScope.runTest { + val prompt by collectLastValue(interactor.prompt) assertThat(prompt).isNull() - - job.cancel() } @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN) @@ -102,12 +96,11 @@ class PromptCredentialInteractorTest : SysuiTestCase() { @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN) private fun useCredentialForPrompt(kind: Int) = - runTest(dispatcher) { + testScope.runTest { val isStealth = false credentialInteractor.stealthMode = isStealth - var prompt: BiometricPromptRequest? = null - val job = launch { interactor.prompt.collect { prompt = it } } + val prompt by collectLastValue(interactor.prompt) val title = "what a prompt" val subtitle = "s" @@ -124,14 +117,12 @@ class PromptCredentialInteractorTest : SysuiTestCase() { challenge = OPERATION_ID ) - val p = prompt as? BiometricPromptRequest.Credential - assertThat(p).isNotNull() - assertThat(p!!.title).isEqualTo(title) - assertThat(p.subtitle).isEqualTo(subtitle) - assertThat(p.description).isEqualTo(description) - assertThat(p.userInfo).isEqualTo(BiometricUserInfo(USER_ID)) - assertThat(p.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID)) - assertThat(p) + assertThat(prompt?.title).isEqualTo(title) + assertThat(prompt?.subtitle).isEqualTo(subtitle) + assertThat(prompt?.description).isEqualTo(description) + assertThat(prompt?.userInfo).isEqualTo(BiometricUserInfo(USER_ID)) + assertThat(prompt?.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID)) + assertThat(prompt) .isInstanceOf( when (kind) { Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java @@ -142,25 +133,25 @@ class PromptCredentialInteractorTest : SysuiTestCase() { else -> throw Exception("wrong kind") } ) - if (p is BiometricPromptRequest.Credential.Pattern) { - assertThat(p.stealthMode).isEqualTo(isStealth) + val pattern = prompt as? BiometricPromptRequest.Credential.Pattern + if (pattern != null) { + assertThat(pattern.stealthMode).isEqualTo(isStealth) } interactor.resetPrompt() assertThat(prompt).isNull() - - job.cancel() } @Test fun checkCredential() = - runTest(dispatcher) { + testScope.runTest { val hat = ByteArray(4) credentialInteractor.verifyCredentialResponse = { _ -> flowOf(verified(hat)) } val errors = mutableListOf<CredentialStatus.Fail?>() val job = launch { interactor.verificationError.toList(errors) } + runCurrent() val checked = interactor.checkCredential(pinRequest(), text = "1234") @@ -168,6 +159,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(checked).isNotNull() assertThat(checked!!.hat).isSameInstanceAs(hat) + + runCurrent() assertThat(errors.map { it?.error }).containsExactly(null) job.cancel() @@ -175,7 +168,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { @Test fun checkCredentialWhenBad() = - runTest(dispatcher) { + testScope.runTest { val errorMessage = "bad" val remainingAttempts = 12 credentialInteractor.verifyCredentialResponse = { _ -> @@ -184,6 +177,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { val errors = mutableListOf<CredentialStatus.Fail?>() val job = launch { interactor.verificationError.toList(errors) } + runCurrent() val checked = interactor.checkCredential(pinRequest(), text = "1234") @@ -192,6 +186,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(checked).isNotNull() assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts) assertThat(checked.urgentMessage).isNull() + + runCurrent() assertThat(errors.map { it?.error }).containsExactly(null, errorMessage).inOrder() job.cancel() @@ -199,7 +195,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { @Test fun checkCredentialWhenBadAndUrgentMessage() = - runTest(dispatcher) { + testScope.runTest { val error = "not so bad" val urgentMessage = "really bad" credentialInteractor.verifyCredentialResponse = { _ -> @@ -208,6 +204,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { val errors = mutableListOf<CredentialStatus.Fail?>() val job = launch { interactor.verificationError.toList(errors) } + runCurrent() val checked = interactor.checkCredential(pinRequest(), text = "1234") @@ -215,6 +212,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(checked).isNotNull() assertThat(checked!!.urgentMessage).isEqualTo(urgentMessage) + + runCurrent() assertThat(errors.map { it?.error }).containsExactly(null, error).inOrder() assertThat(errors.last() as? CredentialStatus.Fail.Error) .isEqualTo(error(error, 10, urgentMessage)) @@ -224,7 +223,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { @Test fun checkCredentialWhenBadAndThrottled() = - runTest(dispatcher) { + testScope.runTest { val remainingAttempts = 3 val error = ":(" val urgentMessage = ":D" @@ -239,6 +238,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { } val errors = mutableListOf<CredentialStatus.Fail?>() val job = launch { interactor.verificationError.toList(errors) } + runCurrent() val checked = interactor.checkCredential(pinRequest(), text = "1234") @@ -246,6 +246,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(checked).isNotNull() assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts) + + runCurrent() assertThat(checked.urgentMessage).isEqualTo(urgentMessage) assertThat(errors.map { it?.error }) .containsExactly(null, "1", "2", "3", error) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt new file mode 100644 index 000000000000..a62ea3b77fc2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -0,0 +1,204 @@ +/* + * 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.biometrics.domain.interactor + +import android.app.admin.DevicePolicyManager +import android.hardware.biometrics.BiometricManager.Authenticators +import android.hardware.biometrics.PromptInfo +import androidx.test.filters.SmallTest +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.data.repository.FakePromptRepository +import com.android.systemui.biometrics.domain.model.BiometricModalities +import com.android.systemui.biometrics.faceSensorPropertiesInternal +import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal +import com.android.systemui.biometrics.shared.model.PromptKind +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.mockito.any +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.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit + +private const val TITLE = "hey there" +private const val SUBTITLE = "ok" +private const val DESCRIPTION = "football" +private const val NEGATIVE_TEXT = "escape" + +private const val USER_ID = 8 +private const val CHALLENGE = 999L + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class PromptSelectorInteractorImplTest : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var lockPatternUtils: LockPatternUtils + + private val testScope = TestScope() + private val promptRepository = FakePromptRepository() + + private lateinit var interactor: PromptSelectorInteractor + + @Before + fun setup() { + interactor = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils) + } + + @Test + fun useBiometricsAndReset() = + testScope.runTest { useBiometricsAndReset(allowCredentialFallback = true) } + + @Test + fun useBiometricsAndResetWithoutFallback() = + testScope.runTest { useBiometricsAndReset(allowCredentialFallback = false) } + + private fun TestScope.useBiometricsAndReset(allowCredentialFallback: Boolean) { + setUserCredentialType(isPassword = true) + + val confirmationRequired = true + val info = + PromptInfo().apply { + title = TITLE + subtitle = SUBTITLE + description = DESCRIPTION + negativeButtonText = NEGATIVE_TEXT + isConfirmationRequested = confirmationRequired + authenticators = + if (allowCredentialFallback) { + Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL + } else { + Authenticators.BIOMETRIC_STRONG + } + isDeviceCredentialAllowed = allowCredentialFallback + } + val modalities = + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal().first(), + faceProperties = faceSensorPropertiesInternal().first(), + ) + + val currentPrompt by collectLastValue(interactor.prompt) + val credentialKind by collectLastValue(interactor.credentialKind) + val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed) + val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequested) + + assertThat(currentPrompt).isNull() + + interactor.useBiometricsForAuthentication( + info, + confirmationRequired, + USER_ID, + CHALLENGE, + modalities + ) + + assertThat(currentPrompt).isNotNull() + assertThat(currentPrompt?.title).isEqualTo(TITLE) + assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION) + assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE) + assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT) + + if (allowCredentialFallback) { + assertThat(credentialKind).isSameInstanceAs(PromptKind.Password) + assertThat(isCredentialAllowed).isTrue() + } else { + assertThat(credentialKind).isEqualTo(PromptKind.Biometric()) + assertThat(isCredentialAllowed).isFalse() + } + assertThat(isExplicitConfirmationRequired).isEqualTo(confirmationRequired) + + interactor.resetPrompt() + verifyUnset() + } + + @Test + fun usePinCredentialAndReset() = + testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) } + + @Test + fun usePattermCredentialAndReset() = + testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) } + + @Test + fun usePasswordCredentialAndReset() = + testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) } + + private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) { + setUserCredentialType( + isPin = kind == Utils.CREDENTIAL_PIN, + isPassword = kind == Utils.CREDENTIAL_PASSWORD, + ) + + val info = + PromptInfo().apply { + title = TITLE + subtitle = SUBTITLE + description = DESCRIPTION + negativeButtonText = NEGATIVE_TEXT + authenticators = Authenticators.DEVICE_CREDENTIAL + isDeviceCredentialAllowed = true + } + + val currentPrompt by collectLastValue(interactor.prompt) + val credentialKind by collectLastValue(interactor.credentialKind) + + assertThat(currentPrompt).isNull() + + interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE) + + // not using biometrics, should be null with no fallback option + assertThat(currentPrompt).isNull() + assertThat(credentialKind).isEqualTo(PromptKind.Biometric()) + + interactor.resetPrompt() + verifyUnset() + } + + private fun TestScope.verifyUnset() { + val currentPrompt by collectLastValue(interactor.prompt) + val credentialKind by collectLastValue(interactor.credentialKind) + + assertThat(currentPrompt).isNull() + + val kind = credentialKind as? PromptKind.Biometric + assertThat(kind).isNotNull() + assertThat(kind?.activeModalities?.isEmpty).isTrue() + } + + private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) { + whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(any())) + .thenReturn( + when { + isPin -> DevicePolicyManager.PASSWORD_QUALITY_NUMERIC + isPassword -> DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC + else -> DevicePolicyManager.PASSWORD_QUALITY_SOMETHING + } + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt new file mode 100644 index 000000000000..526b83356ee9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt @@ -0,0 +1,102 @@ +/* + * 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.biometrics.domain.model + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.faceSensorPropertiesInternal +import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class BiometricModalitiesTest : SysuiTestCase() { + + @Test + fun isEmpty() { + assertThat(BiometricModalities().isEmpty).isTrue() + } + + @Test + fun fingerprintOnly() { + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal().first(), + ) + ) { + assertThat(isEmpty).isFalse() + assertThat(hasFace).isFalse() + assertThat(hasFaceOnly).isFalse() + assertThat(hasFingerprint).isTrue() + assertThat(hasFingerprintOnly).isTrue() + assertThat(hasFaceAndFingerprint).isFalse() + } + } + + @Test + fun faceOnly() { + with(BiometricModalities(faceProperties = faceSensorPropertiesInternal().first())) { + assertThat(isEmpty).isFalse() + assertThat(hasFace).isTrue() + assertThat(hasFaceOnly).isTrue() + assertThat(hasFingerprint).isFalse() + assertThat(hasFingerprintOnly).isFalse() + assertThat(hasFaceAndFingerprint).isFalse() + } + } + + @Test + fun faceStrength() { + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal(strong = false).first(), + faceProperties = faceSensorPropertiesInternal(strong = true).first() + ) + ) { + assertThat(isFaceStrong).isTrue() + } + + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal(strong = false).first(), + faceProperties = faceSensorPropertiesInternal(strong = false).first() + ) + ) { + assertThat(isFaceStrong).isFalse() + } + } + + @Test + fun faceAndFingerprint() { + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal().first(), + faceProperties = faceSensorPropertiesInternal().first(), + ) + ) { + assertThat(isEmpty).isFalse() + assertThat(hasFace).isTrue() + assertThat(hasFingerprint).isTrue() + assertThat(hasFaceOnly).isFalse() + assertThat(hasFingerprintOnly).isFalse() + assertThat(hasFaceAndFingerprint).isTrue() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt index 4c5e3c1bc6a6..e3529055c223 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.biometrics.domain.model import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.promptInfo import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -21,11 +22,13 @@ class BiometricPromptRequestTest : SysuiTestCase() { val subtitle = "a" val description = "request" + val fpPros = fingerprintSensorPropertiesInternal().first() val request = BiometricPromptRequest.Biometric( promptInfo(title = title, subtitle = subtitle, description = description), BiometricUserInfo(USER_ID), - BiometricOperationInfo(OPERATION_ID) + BiometricOperationInfo(OPERATION_ID), + BiometricModalities(fingerprintProperties = fpPros), ) assertThat(request.title).isEqualTo(title) @@ -33,6 +36,8 @@ class BiometricPromptRequestTest : SysuiTestCase() { assertThat(request.description).isEqualTo(description) assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID)) assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID)) + assertThat(request.modalities) + .isEqualTo(BiometricModalities(fingerprintProperties = fpPros)) } @Test @@ -51,19 +56,19 @@ class BiometricPromptRequestTest : SysuiTestCase() { description = description, credentialTitle = null, credentialSubtitle = null, - credentialDescription = null + credentialDescription = null, ), BiometricUserInfo(USER_ID), - BiometricOperationInfo(OPERATION_ID) + BiometricOperationInfo(OPERATION_ID), ), BiometricPromptRequest.Credential.Password( promptInfo( credentialTitle = title, credentialSubtitle = subtitle, - credentialDescription = description + credentialDescription = description, ), BiometricUserInfo(USER_ID), - BiometricOperationInfo(OPERATION_ID) + BiometricOperationInfo(OPERATION_ID), ), BiometricPromptRequest.Credential.Pattern( promptInfo( @@ -71,11 +76,11 @@ class BiometricPromptRequestTest : SysuiTestCase() { description = description, credentialTitle = title, credentialSubtitle = null, - credentialDescription = null + credentialDescription = null, ), BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), - stealth + stealth, ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt index d73cdfc4249f..3245020ec584 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt @@ -2,12 +2,12 @@ package com.android.systemui.biometrics.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.model.PromptKind import com.android.systemui.biometrics.data.repository.FakePromptRepository -import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor import com.android.systemui.biometrics.domain.interactor.CredentialStatus import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor +import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor import com.android.systemui.biometrics.promptInfo +import com.android.systemui.biometrics.shared.model.PromptKind import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf @@ -40,17 +40,13 @@ class CredentialViewModelTest : SysuiTestCase() { viewModel = CredentialViewModel( mContext, - BiometricPromptCredentialInteractor( - dispatcher, - promptRepository, - credentialInteractor - ) + PromptCredentialInteractor(dispatcher, promptRepository, credentialInteractor) ) } - @Test fun setsPinInputFlags() = setsInputFlags(PromptKind.PIN, expectFlags = true) - @Test fun setsPasswordInputFlags() = setsInputFlags(PromptKind.PASSWORD, expectFlags = false) - @Test fun setsPatternInputFlags() = setsInputFlags(PromptKind.PATTERN, expectFlags = false) + @Test fun setsPinInputFlags() = setsInputFlags(PromptKind.Pin, expectFlags = true) + @Test fun setsPasswordInputFlags() = setsInputFlags(PromptKind.Password, expectFlags = false) + @Test fun setsPatternInputFlags() = setsInputFlags(PromptKind.Pattern, expectFlags = false) private fun setsInputFlags(type: PromptKind, expectFlags: Boolean) = runTestWithKind(type) { @@ -65,10 +61,10 @@ class CredentialViewModelTest : SysuiTestCase() { job.cancel() } - @Test fun isStealthIgnoredByPin() = isStealthMode(PromptKind.PIN, expectStealth = false) + @Test fun isStealthIgnoredByPin() = isStealthMode(PromptKind.Pin, expectStealth = false) @Test - fun isStealthIgnoredByPassword() = isStealthMode(PromptKind.PASSWORD, expectStealth = false) - @Test fun isStealthUsedByPattern() = isStealthMode(PromptKind.PATTERN, expectStealth = true) + fun isStealthIgnoredByPassword() = isStealthMode(PromptKind.Password, expectStealth = false) + @Test fun isStealthUsedByPattern() = isStealthMode(PromptKind.Pattern, expectStealth = true) private fun isStealthMode(type: PromptKind, expectStealth: Boolean) = runTestWithKind(type, init = { credentialInteractor.stealthMode = true }) { @@ -119,7 +115,7 @@ class CredentialViewModelTest : SysuiTestCase() { val attestations = mutableListOf<ByteArray?>() val remainingAttempts = mutableListOf<RemainingAttempts?>() - var header: HeaderViewModel? = null + var header: CredentialHeaderViewModel? = null val job = launch { launch { viewModel.validatedAttestation.toList(attestations) } launch { viewModel.remainingAttempts.toList(remainingAttempts) } @@ -147,7 +143,7 @@ class CredentialViewModelTest : SysuiTestCase() { val attestations = mutableListOf<ByteArray?>() val remainingAttempts = mutableListOf<RemainingAttempts?>() - var header: HeaderViewModel? = null + var header: CredentialHeaderViewModel? = null val job = launch { launch { viewModel.validatedAttestation.toList(attestations) } launch { viewModel.remainingAttempts.toList(remainingAttempts) } @@ -169,7 +165,7 @@ class CredentialViewModelTest : SysuiTestCase() { } private fun runTestWithKind( - kind: PromptKind = PromptKind.PIN, + kind: PromptKind = PromptKind.Pin, init: () -> Unit = {}, block: suspend TestScope.() -> Unit, ) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt new file mode 100644 index 000000000000..689bb0023675 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt @@ -0,0 +1,86 @@ +/* + * 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.biometrics.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.domain.model.BiometricModality +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class PromptAuthStateTest : SysuiTestCase() { + + @Test + fun notAuthenticated() { + with(PromptAuthState(isAuthenticated = false)) { + assertThat(isNotAuthenticated).isTrue() + assertThat(isAuthenticatedAndConfirmed).isFalse() + assertThat(isAuthenticatedByFace).isFalse() + assertThat(isAuthenticatedByFingerprint).isFalse() + } + } + + @Test + fun authenticatedByUnknown() { + with(PromptAuthState(isAuthenticated = true)) { + assertThat(isNotAuthenticated).isFalse() + assertThat(isAuthenticatedAndConfirmed).isTrue() + assertThat(isAuthenticatedByFace).isFalse() + assertThat(isAuthenticatedByFingerprint).isFalse() + } + + with(PromptAuthState(isAuthenticated = true, needsUserConfirmation = true)) { + assertThat(isNotAuthenticated).isFalse() + assertThat(isAuthenticatedAndConfirmed).isFalse() + assertThat(isAuthenticatedByFace).isFalse() + assertThat(isAuthenticatedByFingerprint).isFalse() + + assertThat(asConfirmed().isAuthenticatedAndConfirmed).isTrue() + } + } + + @Test + fun authenticatedWithFace() { + with( + PromptAuthState(isAuthenticated = true, authenticatedModality = BiometricModality.Face) + ) { + assertThat(isNotAuthenticated).isFalse() + assertThat(isAuthenticatedAndConfirmed).isTrue() + assertThat(isAuthenticatedByFace).isTrue() + assertThat(isAuthenticatedByFingerprint).isFalse() + } + } + + @Test + fun authenticatedWithFingerprint() { + with( + PromptAuthState( + isAuthenticated = true, + authenticatedModality = BiometricModality.Fingerprint, + ) + ) { + assertThat(isNotAuthenticated).isFalse() + assertThat(isAuthenticatedAndConfirmed).isTrue() + assertThat(isAuthenticatedByFace).isFalse() + assertThat(isAuthenticatedByFingerprint).isTrue() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt new file mode 100644 index 000000000000..3ba6004e4532 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -0,0 +1,639 @@ +/* + * 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.biometrics.ui.viewmodel + +import android.hardware.biometrics.PromptInfo +import android.hardware.face.FaceSensorPropertiesInternal +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import androidx.test.filters.SmallTest +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthBiometricView +import com.android.systemui.biometrics.data.repository.FakePromptRepository +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl +import com.android.systemui.biometrics.domain.model.BiometricModalities +import com.android.systemui.biometrics.domain.model.BiometricModality +import com.android.systemui.biometrics.extractAuthenticatorTypes +import com.android.systemui.biometrics.faceSensorPropertiesInternal +import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal +import com.android.systemui.coroutines.collectLastValue +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit + +private const val USER_ID = 4 +private const val CHALLENGE = 2L + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(Parameterized::class) +internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var lockPatternUtils: LockPatternUtils + + private val testScope = TestScope() + private val promptRepository = FakePromptRepository() + + private lateinit var selector: PromptSelectorInteractor + private lateinit var viewModel: PromptViewModel + + @Before + fun setup() { + selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils) + selector.resetPrompt() + + viewModel = PromptViewModel(selector) + } + + @Test + fun `start idle and show authenticating`() = + runGenericTest(doNotStart = true) { + val expectedSize = + if (testCase.shouldStartAsImplicitFlow) PromptSize.SMALL else PromptSize.MEDIUM + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val modalities by collectLastValue(viewModel.modalities) + val message by collectLastValue(viewModel.message) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isNotAuthenticated).isTrue() + with(modalities ?: throw Exception("missing modalities")) { + assertThat(hasFace).isEqualTo(testCase.face != null) + assertThat(hasFingerprint).isEqualTo(testCase.fingerprint != null) + } + assertThat(message).isEqualTo(PromptMessage.Empty) + assertThat(size).isEqualTo(expectedSize) + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN) + + val startMessage = "here we go" + viewModel.showAuthenticating(startMessage, isRetry = false) + + assertThat(message).isEqualTo(PromptMessage.Help(startMessage)) + assertThat(authenticating).isTrue() + assertThat(authenticated?.isNotAuthenticated).isTrue() + assertThat(size).isEqualTo(expectedSize) + assertButtonsVisible(negative = expectedSize != PromptSize.SMALL) + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING) + } + + @Test + fun `shows authenticated - no errors`() = runGenericTest { + // this case can't happen until fingerprint is started + // trigger it now since no error has occurred in this test + val forceError = testCase.isCoex && testCase.authenticatedByFingerprint + + if (forceError) { + assertThat(viewModel.fingerprintStartMode.first()) + .isEqualTo(FingerprintStartMode.Pending) + viewModel.ensureFingerprintHasStarted(isDelayed = true) + } + + showAuthenticated( + testCase.authenticatedModality, + testCase.expectConfirmation(atLeastOneFailure = forceError), + ) + } + + private suspend fun TestScope.showAuthenticated( + authenticatedModality: BiometricModality, + expectConfirmation: Boolean, + ) { + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val fpStartMode by collectLastValue(viewModel.fingerprintStartMode) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + + val authWithSmallPrompt = + testCase.shouldStartAsImplicitFlow && + (fpStartMode == FingerprintStartMode.Pending || testCase.isFaceOnly) + assertThat(authenticating).isTrue() + assertThat(authenticated?.isNotAuthenticated).isTrue() + assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM) + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING) + assertButtonsVisible(negative = !authWithSmallPrompt) + + val delay = 1000L + viewModel.showAuthenticated(authenticatedModality, delay) + + assertThat(authenticated?.isAuthenticated).isTrue() + assertThat(authenticated?.delay).isEqualTo(delay) + assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) + assertThat(size) + .isEqualTo( + if (authenticatedModality == BiometricModality.Fingerprint || expectConfirmation) { + PromptSize.MEDIUM + } else { + PromptSize.SMALL + } + ) + assertThat(legacyState) + .isEqualTo( + if (expectConfirmation) { + AuthBiometricView.STATE_PENDING_CONFIRMATION + } else { + AuthBiometricView.STATE_AUTHENTICATED + } + ) + assertButtonsVisible( + cancel = expectConfirmation, + confirm = expectConfirmation, + ) + } + + @Test + fun `shows temporary errors`() = runGenericTest { + val checkAtEnd = suspend { assertButtonsVisible(negative = true) } + + showTemporaryErrors(restart = false) { checkAtEnd() } + showTemporaryErrors(restart = false, helpAfterError = "foo") { checkAtEnd() } + showTemporaryErrors(restart = true) { checkAtEnd() } + } + + private suspend fun TestScope.showTemporaryErrors( + restart: Boolean, + helpAfterError: String = "", + block: suspend TestScope.() -> Unit = {}, + ) { + val errorMessage = "oh no!" + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow) + + val errorJob = launch { + viewModel.showTemporaryError( + errorMessage, + authenticateAfterError = restart, + messageAfterError = helpAfterError, + ) + } + + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertThat(message).isEqualTo(PromptMessage.Error(errorMessage)) + assertThat(messageVisible).isTrue() + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_ERROR) + + // temporary error should disappear after a delay + errorJob.join() + if (helpAfterError.isNotBlank()) { + assertThat(message).isEqualTo(PromptMessage.Help(helpAfterError)) + assertThat(messageVisible).isTrue() + } else { + assertThat(message).isEqualTo(PromptMessage.Empty) + assertThat(messageVisible).isFalse() + } + assertThat(legacyState) + .isEqualTo( + if (restart) { + AuthBiometricView.STATE_AUTHENTICATING + } else { + AuthBiometricView.STATE_HELP + } + ) + + assertThat(authenticating).isEqualTo(restart) + assertThat(authenticated?.isNotAuthenticated).isTrue() + assertThat(canTryAgainNow).isFalse() + + block() + } + + @Test + fun `no errors or temporary help after authenticated`() = runGenericTest { + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val messageIsShowing by collectLastValue(viewModel.isIndicatorMessageVisible) + val canTryAgain by collectLastValue(viewModel.canTryAgainNow) + + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + val verifyNoError = { + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + assertThat(message).isEqualTo(PromptMessage.Empty) + assertThat(canTryAgain).isFalse() + } + + val errorJob = launch { viewModel.showTemporaryError("error") } + verifyNoError() + errorJob.join() + verifyNoError() + + val helpJob = launch { viewModel.showTemporaryHelp("hi") } + verifyNoError() + helpJob.join() + verifyNoError() + + // persistent help is allowed + val stickyHelpMessage = "blah" + viewModel.showHelp(stickyHelpMessage) + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + assertThat(message).isEqualTo(PromptMessage.Help(stickyHelpMessage)) + assertThat(messageIsShowing).isTrue() + } + + // @Test + fun `suppress errors`() = runGenericTest { + val errorMessage = "woot" + val message by collectLastValue(viewModel.message) + + val errorJob = launch { viewModel.showTemporaryError(errorMessage) } + } + + @Test + fun `authenticated at most once`() = runGenericTest { + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + } + + @Test + fun `authenticating cannot restart after authenticated`() = runGenericTest { + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + + viewModel.showAuthenticating("again!") + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + } + + @Test + fun `confirm authentication`() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + val canTryAgain by collectLastValue(viewModel.canTryAgainNow) + + assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) + if (expectConfirmation) { + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertButtonsVisible( + cancel = true, + confirm = true, + ) + + viewModel.confirmAuthenticated() + assertThat(message).isEqualTo(PromptMessage.Empty) + assertButtonsVisible() + } + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED) + assertThat(canTryAgain).isFalse() + } + + @Test + fun `cannot confirm unless authenticated`() = runGenericTest { + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + + viewModel.confirmAuthenticated() + assertThat(authenticating).isTrue() + assertThat(authenticated?.isNotAuthenticated).isTrue() + + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + // reconfirm should be a no-op + viewModel.confirmAuthenticated() + viewModel.confirmAuthenticated() + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isNotAuthenticated).isFalse() + } + + @Test + fun `shows help - before authenticated`() = runGenericTest { + val helpMessage = "please help yourself to some cookies" + val message by collectLastValue(viewModel.message) + val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + + viewModel.showHelp(helpMessage) + + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_HELP) + assertThat(message).isEqualTo(PromptMessage.Help(helpMessage)) + assertThat(messageVisible).isTrue() + + assertThat(viewModel.isAuthenticating.first()).isFalse() + assertThat(viewModel.isAuthenticated.first().isNotAuthenticated).isTrue() + } + + @Test + fun `shows help - after authenticated`() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + val helpMessage = "more cookies please" + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) + val size by collectLastValue(viewModel.size) + val legacyState by collectLastValue(viewModel.legacyState) + + if (testCase.isCoex && testCase.authenticatedByFingerprint) { + viewModel.ensureFingerprintHasStarted(isDelayed = true) + } + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + viewModel.showHelp(helpMessage) + + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION) + assertThat(message).isEqualTo(PromptMessage.Help(helpMessage)) + assertThat(messageVisible).isTrue() + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) + assertButtonsVisible( + cancel = expectConfirmation, + confirm = expectConfirmation, + ) + } + + @Test + fun `retries after failure`() = runGenericTest { + val errorMessage = "bad" + val helpMessage = "again?" + val expectTryAgainButton = testCase.isFaceOnly + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) + val canTryAgain by collectLastValue(viewModel.canTryAgainNow) + + viewModel.showAuthenticating("go") + val errorJob = launch { + viewModel.showTemporaryError( + errorMessage, + messageAfterError = helpMessage, + authenticateAfterError = false, + failedModality = testCase.authenticatedModality + ) + } + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isFalse() + assertThat(message).isEqualTo(PromptMessage.Error(errorMessage)) + assertThat(messageVisible).isTrue() + assertThat(canTryAgain).isEqualTo(testCase.authenticatedByFace) + assertButtonsVisible(negative = true, tryAgain = expectTryAgainButton) + + errorJob.join() + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isFalse() + assertThat(message).isEqualTo(PromptMessage.Help(helpMessage)) + assertThat(messageVisible).isTrue() + assertThat(canTryAgain).isEqualTo(testCase.authenticatedByFace) + assertButtonsVisible(negative = true, tryAgain = expectTryAgainButton) + + val helpMessage2 = "foo" + viewModel.showAuthenticating(helpMessage2, isRetry = true) + assertThat(authenticating).isTrue() + assertThat(authenticated?.isAuthenticated).isFalse() + assertThat(message).isEqualTo(PromptMessage.Help(helpMessage2)) + assertThat(messageVisible).isTrue() + assertButtonsVisible(negative = true) + } + + @Test + fun `switch to credential fallback`() = runGenericTest { + val size by collectLastValue(viewModel.size) + + // TODO(b/251476085): remove Spaghetti, migrate logic, and update this test + viewModel.onSwitchToCredential() + + assertThat(size).isEqualTo(PromptSize.LARGE) + } + + /** Asserts that the selected buttons are visible now. */ + private suspend fun TestScope.assertButtonsVisible( + tryAgain: Boolean = false, + confirm: Boolean = false, + cancel: Boolean = false, + negative: Boolean = false, + credential: Boolean = false, + ) { + runCurrent() + assertThat(viewModel.isTryAgainButtonVisible.first()).isEqualTo(tryAgain) + assertThat(viewModel.isConfirmButtonVisible.first()).isEqualTo(confirm) + assertThat(viewModel.isCancelButtonVisible.first()).isEqualTo(cancel) + assertThat(viewModel.isNegativeButtonVisible.first()).isEqualTo(negative) + assertThat(viewModel.isCredentialButtonVisible.first()).isEqualTo(credential) + } + + private fun runGenericTest( + doNotStart: Boolean = false, + allowCredentialFallback: Boolean = false, + block: suspend TestScope.() -> Unit + ) { + selector.initializePrompt( + requireConfirmation = testCase.confirmationRequested, + allowCredentialFallback = allowCredentialFallback, + fingerprint = testCase.fingerprint, + face = testCase.face, + ) + + // put the view model in the initial authenticating state, unless explicitly skipped + val startMode = + when { + doNotStart -> null + testCase.isCoex -> FingerprintStartMode.Delayed + else -> FingerprintStartMode.Normal + } + when (startMode) { + FingerprintStartMode.Normal -> { + viewModel.ensureFingerprintHasStarted(isDelayed = false) + viewModel.showAuthenticating() + } + FingerprintStartMode.Delayed -> { + viewModel.showAuthenticating() + } + else -> { + /* skip */ + } + } + + testScope.runTest { block() } + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): Collection<TestCase> = singleModalityTestCases + coexTestCases + + private val singleModalityTestCases = + listOf( + TestCase( + face = faceSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Face, + ), + TestCase( + fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Fingerprint, + ), + TestCase( + face = faceSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Face, + confirmationRequested = true, + ), + TestCase( + fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Fingerprint, + confirmationRequested = true, + ), + ) + + private val coexTestCases = + listOf( + TestCase( + face = faceSensorPropertiesInternal(strong = true).first(), + fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Face, + ), + TestCase( + face = faceSensorPropertiesInternal(strong = true).first(), + fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Fingerprint, + ), + TestCase( + face = faceSensorPropertiesInternal(strong = true).first(), + fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Face, + confirmationRequested = true, + ), + TestCase( + face = faceSensorPropertiesInternal(strong = true).first(), + fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + authenticatedModality = BiometricModality.Fingerprint, + confirmationRequested = true, + ), + ) + } +} + +internal data class TestCase( + val fingerprint: FingerprintSensorPropertiesInternal? = null, + val face: FaceSensorPropertiesInternal? = null, + val authenticatedModality: BiometricModality, + val confirmationRequested: Boolean = false, +) { + override fun toString(): String { + val modality = + when { + fingerprint != null && face != null -> "coex" + fingerprint != null -> "fingerprint only" + face != null -> "face only" + else -> "?" + } + return "[$modality, by: $authenticatedModality, confirm: $confirmationRequested]" + } + + fun expectConfirmation(atLeastOneFailure: Boolean): Boolean = + when { + isCoex && authenticatedModality == BiometricModality.Face -> + atLeastOneFailure || confirmationRequested + isFaceOnly -> confirmationRequested + else -> false + } + + val authenticatedByFingerprint: Boolean + get() = authenticatedModality == BiometricModality.Fingerprint + + val authenticatedByFace: Boolean + get() = authenticatedModality == BiometricModality.Face + + val isFaceOnly: Boolean + get() = face != null && fingerprint == null + + val isFingerprintOnly: Boolean + get() = face == null && fingerprint != null + + val isCoex: Boolean + get() = face != null && fingerprint != null + + val shouldStartAsImplicitFlow: Boolean + get() = (isFaceOnly || isCoex) && !confirmationRequested +} + +/** Initialize the test by selecting the give [fingerprint] or [face] configuration(s). */ +private fun PromptSelectorInteractor.initializePrompt( + fingerprint: FingerprintSensorPropertiesInternal? = null, + face: FaceSensorPropertiesInternal? = null, + requireConfirmation: Boolean = false, + allowCredentialFallback: Boolean = false, +) { + val info = + PromptInfo().apply { + title = "t" + subtitle = "s" + authenticators = listOf(face, fingerprint).extractAuthenticatorTypes() + isDeviceCredentialAllowed = allowCredentialFallback + isConfirmationRequested = requireConfirmation + } + useBiometricsForAuthentication( + info, + requireConfirmation, + USER_ID, + CHALLENGE, + BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 730f89dd76ba..9f5c181c3129 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -75,7 +76,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) underTest.clearMessage() - assertThat(message).isNull() + assertThat(message).isEmpty() underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -107,7 +108,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) underTest.clearMessage() - assertThat(message).isNull() + assertThat(message).isEmpty() underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -139,7 +140,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) underTest.clearMessage() - assertThat(message).isNull() + assertThat(message).isEmpty() underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -201,6 +202,56 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(customMessage) } + @Test + fun throttling() = + testScope.runTest { + val throttling by collectLastValue(underTest.throttling) + val message by collectLastValue(underTest.message) + val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + assertThat(throttling).isNull() + assertThat(message).isEqualTo("") + assertThat(isUnlocked).isFalse() + repeat(BouncerInteractor.THROTTLE_EVERY) { times -> + // Wrong PIN. + underTest.authenticate(listOf(6, 7, 8, 9)) + if (times < BouncerInteractor.THROTTLE_EVERY - 1) { + assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) + } + } + assertThat(throttling).isNotNull() + assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) + + // Correct PIN, but throttled, so doesn't unlock: + underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(isUnlocked).isFalse() + assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) + + throttling?.totalDurationSec?.let { seconds -> + repeat(seconds) { time -> + advanceTimeBy(1000) + val remainingTime = seconds - time - 1 + if (remainingTime > 0) { + assertTryAgainMessage(message, remainingTime) + } + } + } + assertThat(message).isEqualTo("") + assertThat(throttling).isNull() + assertThat(isUnlocked).isFalse() + + // Correct PIN and no longer throttled so unlocks: + underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(isUnlocked).isTrue() + } + + private fun assertTryAgainMessage( + message: String?, + time: Int, + ) { + assertThat(message).isEqualTo("Try again in $time seconds.") + } + companion object { private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN" private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password" diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 954e67d77181..b942ccbb51f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -19,11 +19,15 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -40,13 +44,12 @@ class BouncerViewModelTest : SysuiTestCase() { utils.authenticationInteractor( repository = utils.authenticationRepository(), ) - private val underTest = - utils.bouncerViewModel( - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) + private val bouncerInteractor = + utils.bouncerInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), ) + private val underTest = utils.bouncerViewModel(bouncerInteractor) @Test fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() = @@ -89,6 +92,65 @@ class BouncerViewModelTest : SysuiTestCase() { .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet()) } + @Test + fun isMessageUpdateAnimationsEnabled() = + testScope.runTest { + val isMessageUpdateAnimationsEnabled by + collectLastValue(underTest.isMessageUpdateAnimationsEnabled) + val throttling by collectLastValue(bouncerInteractor.throttling) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + assertThat(isMessageUpdateAnimationsEnabled).isTrue() + + repeat(BouncerInteractor.THROTTLE_EVERY) { + // Wrong PIN. + bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) + } + assertThat(isMessageUpdateAnimationsEnabled).isFalse() + + throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } + assertThat(isMessageUpdateAnimationsEnabled).isTrue() + } + + @Test + fun isInputEnabled() = + testScope.runTest { + val isInputEnabled by + collectLastValue( + underTest.authMethod.flatMapLatest { authViewModel -> + authViewModel?.isInputEnabled ?: emptyFlow() + } + ) + val throttling by collectLastValue(bouncerInteractor.throttling) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + assertThat(isInputEnabled).isTrue() + + repeat(BouncerInteractor.THROTTLE_EVERY) { + // Wrong PIN. + bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) + } + assertThat(isInputEnabled).isFalse() + + throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } + assertThat(isInputEnabled).isTrue() + } + + @Test + fun throttlingDialogMessage() = + testScope.runTest { + val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + + repeat(BouncerInteractor.THROTTLE_EVERY) { + // Wrong PIN. + assertThat(throttlingDialogMessage).isNull() + bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) + } + assertThat(throttlingDialogMessage).isNotEmpty() + + underTest.onThrottlingDialogDismissed() + assertThat(throttlingDialogMessage).isNull() + } + private fun authMethodsToTest(): List<AuthenticationMethodModel> { return listOf( AuthenticationMethodModel.None, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index e48b6386c739..b7b90de3b54a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -26,6 +26,8 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before @@ -57,6 +59,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val underTest = PasswordBouncerViewModel( interactor = bouncerInteractor, + isInputEnabled = MutableStateFlow(true).asStateFlow(), ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 6ce29e67982c..b588ba2b2574 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -27,6 +27,8 @@ import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before @@ -60,6 +62,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { applicationContext = context, applicationScope = testScope.backgroundScope, interactor = bouncerInteractor, + isInputEnabled = MutableStateFlow(true).asStateFlow(), ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index bb28520ad8a0..83f9687d7ac5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -27,6 +27,8 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest @@ -68,6 +70,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { PinBouncerViewModel( applicationScope = testScope.backgroundScope, interactor = bouncerInteractor, + isInputEnabled = MutableStateFlow(true).asStateFlow(), ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index 548d26f2aaed..78a65a8473db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -98,7 +98,8 @@ class ResourceTrimmerTest : SysuiTestCase() { ) testScope.runCurrent() verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL) } @Test @@ -115,7 +116,8 @@ class ResourceTrimmerTest : SysuiTestCase() { ) testScope.runCurrent() verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL) } @Test @@ -161,7 +163,8 @@ class ResourceTrimmerTest : SysuiTestCase() { keyguardRepository.setDozeAmount(1f) testScope.runCurrent() verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 3bcefcf6ffff..56698e0ec41c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -41,7 +41,6 @@ import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.R @@ -133,7 +132,6 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock lateinit var activityStarter: ActivityStarter @Mock lateinit var smartspaceManager: SmartspaceManager @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock lateinit var statusBarService: IStatusBarService lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget @Mock private lateinit var mediaRecommendationItem: SmartspaceAction @@ -197,7 +195,6 @@ class MediaDataManagerTest : SysuiTestCase() { logger = logger, smartspaceManager = smartspaceManager, keyguardUpdateMonitor = keyguardUpdateMonitor, - statusBarService = statusBarService, ) verify(tunerService) .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) @@ -522,143 +519,12 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test - fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() { - // When the manager has a notification with an empty title, and the app is required - // to include a non-empty title - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is not added and we report a notification error - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() { - // When the manager has a notification with a blank title, and the app is required - // to include a non-empty title - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is not added and we report a notification error - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() { - // When the app is required to provide a non-blank title, and updates a previously valid - // title to an empty one - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - addNotificationAndLoad() - val data = mediaDataCaptor.value - - verify(listener) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - - reset(listener) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is removed - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) - } - - @Test - fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() { + fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { // When the manager has a notification with an empty title, and the app is not // required to include a non-empty title val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false) whenever(controller.metadata) .thenReturn( metadataBuilder @@ -684,13 +550,12 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test - fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() { + fun testOnNotificationAdded_blankTitle_hasPlaceholder() { // GIVEN that the manager has a notification with a blank title, and the app is not // required to include a non-empty title val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false) whenever(controller.metadata) .thenReturn( metadataBuilder @@ -722,7 +587,6 @@ class MediaDataManagerTest : SysuiTestCase() { val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) whenever(controller.metadata) .thenReturn( metadataBuilder diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 8b070ef87504..079ef372cae6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -63,6 +63,7 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.SecureSettings import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles @@ -649,7 +650,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun onRoleHoldersChanged_notesRole_sameUser_shouldUpdateShortcuts() { + fun onRoleHoldersChanged_notesRole_shouldUpdateShortcuts() { val user = userTracker.userHandle val controller = spy(createNoteTaskController()) doNothing().whenever(controller).updateNoteTaskAsUser(any()) @@ -658,22 +659,41 @@ internal class NoteTaskControllerTest : SysuiTestCase() { verify(controller).updateNoteTaskAsUser(user) } + // endregion + + // region updateNoteTaskAsUser + @Test + fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() { + val user = userTracker.userHandle + val controller = spy(createNoteTaskController()) + doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) + + controller.updateNoteTaskAsUser(user) + + verify(controller).updateNoteTaskAsUserInternal(user) + verify(context, never()).startServiceAsUser(any(), any()) + } @Test - fun onRoleHoldersChanged_notesRole_differentUser_shouldUpdateShortcutsInUserProcess() { + fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() { // FakeUserTracker will default to UserHandle.SYSTEM. val user = UserHandle.CURRENT + val controller = spy(createNoteTaskController(isEnabled = true)) + doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) - createNoteTaskController(isEnabled = true).onRoleHoldersChanged(ROLE_NOTES, user) + controller.updateNoteTaskAsUser(user) - verify(context).startServiceAsUser(any(), eq(user)) + verify(controller, never()).updateNoteTaskAsUserInternal(any()) + val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) } + assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java) } // endregion - // region updateNoteTaskAsUser + // region internalUpdateNoteTaskAsUser @Test - fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() { - createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle) + fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() { + createNoteTaskController(isEnabled = true) + .updateNoteTaskAsUserInternal(userTracker.userHandle) val actualComponent = argumentCaptor<ComponentName>() verify(context.packageManager) @@ -702,11 +722,12 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun updateNoteTaskAsUser_noNotesRole_shouldDisableShortcuts() { + fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() { whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle)) .thenReturn(emptyList()) - createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle) + createNoteTaskController(isEnabled = true) + .updateNoteTaskAsUserInternal(userTracker.userHandle) val argument = argumentCaptor<ComponentName>() verify(context.packageManager) @@ -723,8 +744,9 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun updateNoteTaskAsUser_flagDisabled_shouldDisableShortcuts() { - createNoteTaskController(isEnabled = false).updateNoteTaskAsUser(userTracker.userHandle) + fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() { + createNoteTaskController(isEnabled = false) + .updateNoteTaskAsUserInternal(userTracker.userHandle) val argument = argumentCaptor<ComponentName>() verify(context.packageManager) @@ -741,6 +763,20 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } // endregion + // startregion updateNoteTaskForAllUsers + @Test + fun updateNoteTaskForAllUsers_shouldRunUpdateForCurrentUserAndProfiles() { + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + val controller = spy(createNoteTaskController()) + doNothing().whenever(controller).updateNoteTaskAsUser(any()) + + controller.updateNoteTaskForCurrentUserAndManagedProfiles() + + verify(controller).updateNoteTaskAsUser(mainUserInfo.userHandle) + verify(controller).updateNoteTaskAsUser(workUserInfo.userHandle) + } + // endregion + // region getUserForHandlingNotesTaking @Test fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 4e85b6c555ef..95bb3e0a4538 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -32,9 +32,12 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.FakeSystemClock import com.android.wm.shell.bubbles.Bubbles +import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before @@ -71,19 +74,19 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } private fun createUnderTest( - isEnabled: Boolean, - bubbles: Bubbles?, + isEnabled: Boolean, + bubbles: Bubbles?, ): NoteTaskInitializer = - NoteTaskInitializer( - controller = controller, - commandQueue = commandQueue, - optionalBubbles = Optional.ofNullable(bubbles), - isEnabled = isEnabled, - roleManager = roleManager, - userTracker = userTracker, - keyguardUpdateMonitor = keyguardMonitor, - backgroundExecutor = executor, - ) + NoteTaskInitializer( + controller = controller, + commandQueue = commandQueue, + optionalBubbles = Optional.ofNullable(bubbles), + isEnabled = isEnabled, + roleManager = roleManager, + userTracker = userTracker, + keyguardUpdateMonitor = keyguardMonitor, + backgroundExecutor = executor, + ) @Test fun initialize_withUserUnlocked() { @@ -93,8 +96,8 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { verify(commandQueue).addCallback(any()) verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) - verify(controller).setNoteTaskShortcutEnabled(any(), any()) - verify(keyguardMonitor, never()).registerCallback(any()) + verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles() + verify(keyguardMonitor).registerCallback(any()) } @Test @@ -107,6 +110,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) verify(controller, never()).setNoteTaskShortcutEnabled(any(), any()) verify(keyguardMonitor).registerCallback(any()) + assertThat(userTracker.callbacks).isNotEmpty() } @Test @@ -116,12 +120,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { underTest.initialize() verifyZeroInteractions( - commandQueue, - bubbles, - controller, - roleManager, - userManager, - keyguardMonitor, + commandQueue, + bubbles, + controller, + roleManager, + userManager, + keyguardMonitor, ) } @@ -132,12 +136,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { underTest.initialize() verifyZeroInteractions( - commandQueue, - bubbles, - controller, - roleManager, - userManager, - keyguardMonitor, + commandQueue, + bubbles, + controller, + roleManager, + userManager, + keyguardMonitor, ) } @@ -146,7 +150,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { val expectedKeyEvent = KeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL) val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() - val callback = captureArgument { verify(commandQueue).addCallback(capture()) } + val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) } callback.handleSystemKey(expectedKeyEvent) @@ -154,31 +158,49 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test - fun initialize_userUnlocked() { + fun initialize_userUnlocked_shouldUpdateNoteTask() { whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(false) val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() - val callback = captureArgument { verify(keyguardMonitor).registerCallback(capture()) } + val callback = withArgCaptor { verify(keyguardMonitor).registerCallback(capture()) } whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true) callback.onUserUnlocked() - verify(controller).setNoteTaskShortcutEnabled(any(), any()) + + verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles() } @Test - fun initialize_onRoleHoldersChanged() { + fun initialize_onRoleHoldersChanged_shouldRunOnRoleHoldersChanged() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() - val callback = captureArgument { + val callback = withArgCaptor { verify(roleManager) - .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL)) + .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL)) } callback.onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle) verify(controller).onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle) } -} -private inline fun <reified T : Any> captureArgument(block: ArgumentCaptor<T>.() -> Unit) = - argumentCaptor<T>().apply(block).value + @Test + fun initialize_onProfilesChanged_shouldUpdateNoteTask() { + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + + userTracker.callbacks.first().onProfilesChanged(emptyList()) + + verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles() + } + + @Test + fun initialize_onUserChanged_shouldUpdateNoteTask() { + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + + userTracker.callbacks.first().onUserChanged(0, mock()) + + verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt index 2eca78a0412b..e92368df8663 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.shared.clocks import android.testing.AndroidTestingRunner import android.view.LayoutInflater import androidx.test.filters.SmallTest +import com.android.app.animation.Interpolators import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.TextAnimator @@ -64,8 +65,8 @@ class AnimatableClockViewTest : SysuiTestCase() { color = 200, strokeWidth = -1F, animate = false, - duration = 350L, - interpolator = null, + duration = 833L, + interpolator = Interpolators.EMPHASIZED_DECELERATE, delay = 0L, onAnimationEnd = null ) @@ -98,8 +99,8 @@ class AnimatableClockViewTest : SysuiTestCase() { color = 200, strokeWidth = -1F, animate = true, - duration = 350L, - interpolator = null, + duration = 833L, + interpolator = Interpolators.EMPHASIZED_DECELERATE, delay = 0L, onAnimationEnd = null ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index f4cd383f7c4c..1643e174ee13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -28,7 +28,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.ComponentName; import android.graphics.Rect; -import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; @@ -443,15 +442,13 @@ public class CommandQueueTest extends SysuiTestCase { final long operationId = 1; final String packageName = "test"; final long requestId = 10; - final int multiSensorConfig = BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds, - credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId, - multiSensorConfig); + credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId); waitForIdleSync(); verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds), eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(operationId), - eq(packageName), eq(requestId), eq(multiSensorConfig)); + eq(packageName), eq(requestId)); } @Test 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 551499e0fb55..7632d01d4d43 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 @@ -392,23 +392,32 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { @Test public void testSnapchild_targetIsZero() { - doNothing().when(mSwipeHelper).superSnapChild(mView, 0, 0); - mSwipeHelper.snapChild(mView, 0, 0); + doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 0, 0); + mSwipeHelper.snapChild(mNotificationRow, 0, 0); - verify(mCallback, times(1)).onDragCancelled(mView); - verify(mSwipeHelper, times(1)).superSnapChild(mView, 0, 0); + verify(mCallback, times(1)).onDragCancelled(mNotificationRow); + verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 0, 0); verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed(); } @Test public void testSnapchild_targetNotZero() { + doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 10, 0); + mSwipeHelper.snapChild(mNotificationRow, 10, 0); + + verify(mCallback, times(1)).onDragCancelled(mNotificationRow); + verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 10, 0); + verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed(); + } + + @Test + public void testSnapchild_targetNotSwipeable() { doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0); mSwipeHelper.snapChild(mView, 10, 0); - verify(mCallback, times(1)).onDragCancelled(mView); - verify(mSwipeHelper, times(1)).superSnapChild(mView, 10, 0); - verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed(); + verify(mCallback).onDragCancelled(mView); + verify(mSwipeHelper, never()).superSnapChild(mView, 10, 0); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 45a37cffa588..8f725bebfb16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -35,7 +35,6 @@ import android.app.KeyguardManager; import android.content.res.Configuration; import android.media.AudioManager; import android.os.SystemClock; -import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Gravity; @@ -47,7 +46,6 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.Prefs; import com.android.systemui.R; @@ -62,9 +60,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.FakeConfigurationController; -import com.android.systemui.util.DeviceConfigProxyFake; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -88,8 +83,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { View mDrawerVibrate; View mDrawerMute; View mDrawerNormal; - private DeviceConfigProxyFake mDeviceConfigProxy; - private FakeExecutor mExecutor; private TestableLooper mTestableLooper; private ConfigurationController mConfigurationController; private int mOriginalOrientation; @@ -131,8 +124,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { getContext().addMockSystemService(KeyguardManager.class, mKeyguard); mTestableLooper = TestableLooper.get(this); - mDeviceConfigProxy = new DeviceConfigProxyFake(); - mExecutor = new FakeExecutor(new FakeSystemClock()); when(mPostureController.getDevicePosture()) .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); @@ -151,8 +142,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, mPostureController, mTestableLooper.getLooper(), @@ -173,9 +162,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1); Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); - - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); } private State createShellState() { @@ -351,13 +337,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { * API does not exist. So we do the next best thing; we check the cached icon id. */ @Test - public void notificationVolumeSeparated_theRingerIconChanges() { - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); - - mExecutor.runAllReady(); // for the config change to take effect - - // assert icon is new based on res id + public void notificationVolumeSeparated_theRingerIconChangesToSpeakerIcon() { + // already separated. assert icon is new based on res id assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_speaker_on); assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, @@ -365,17 +346,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() { - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); - - mExecutor.runAllReady(); - - assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer); - assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute); - } - - @Test public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() { mDialog.dismissH(DISMISS_REASON_UNKNOWN); // notifyVisible(false) should not be called immediately but only after the dismiss @@ -408,8 +378,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), @@ -447,8 +415,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), @@ -485,8 +451,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), @@ -525,8 +489,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, mPostureController, mTestableLooper.getLooper(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt index 96658c61109d..d270700aa856 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt @@ -1,7 +1,7 @@ package com.android.systemui.biometrics.data.repository import android.hardware.biometrics.PromptInfo -import com.android.systemui.biometrics.data.model.PromptKind +import com.android.systemui.biometrics.shared.model.PromptKind import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -20,26 +20,32 @@ class FakePromptRepository : PromptRepository { private var _challenge = MutableStateFlow<Long?>(null) override val challenge = _challenge.asStateFlow() - private val _kind = MutableStateFlow(PromptKind.ANY_BIOMETRIC) + private val _kind = MutableStateFlow<PromptKind>(PromptKind.Biometric()) override val kind = _kind.asStateFlow() + private val _isConfirmationRequired = MutableStateFlow(false) + override val isConfirmationRequired = _isConfirmationRequired.asStateFlow() + override fun setPrompt( promptInfo: PromptInfo, userId: Int, gatekeeperChallenge: Long?, - kind: PromptKind + kind: PromptKind, + requireConfirmation: Boolean, ) { _promptInfo.value = promptInfo _userId.value = userId _challenge.value = gatekeeperChallenge _kind.value = kind + _isConfirmationRequired.value = requireConfirmation } override fun unsetPrompt() { _promptInfo.value = null _userId.value = null _challenge.value = null - _kind.value = PromptKind.ANY_BIOMETRIC + _kind.value = PromptKind.Biometric() + _isConfirmationRequired.value = false } fun setIsShowing(showing: Boolean) { diff --git a/services/Android.bp b/services/Android.bp index b0a0e5e44a8c..7b64b4714017 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -58,7 +58,12 @@ system_optimized_java_defaults { optimize: false, shrink: true, ignore_warnings: false, - proguard_flags_files: ["proguard.flags"], + proguard_flags_files: [ + "proguard.flags", + // Ensure classes referenced in the framework-res manifest + // and implemented in system_server are kept. + ":framework-res{.aapt.proguardOptionsFile}", + ], }, conditions_default: { optimize: { diff --git a/services/accessibility/lint-baseline.xml b/services/accessibility/lint-baseline.xml new file mode 100644 index 000000000000..6bec8cf5f018 --- /dev/null +++ b/services/accessibility/lint-baseline.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev"> + + <issue + id="SimpleManualPermissionEnforcement" + message="IAccessibilityManager permission check should be converted to @EnforcePermission annotation" + errorLine1=" mContext.enforceCallingOrSelfPermission(" + errorLine2=" ^"> + <location + file="frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java" + line="3992" + column="9"/> + </issue> + + <issue + id="SimpleManualPermissionEnforcement" + message="IAccessibilityManager permission check should be converted to @EnforcePermission annotation" + errorLine1=" mContext.enforceCallingOrSelfPermission(" + errorLine2=" ^"> + <location + file="frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java" + line="4007" + column="9"/> + </issue> + +</issues> diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index 4aeb4a4f8409..cd6de8749f47 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -349,7 +349,8 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { private int isFieldDetectionServiceEnabled(PrintWriter pw) { final int userId = getNextIntArgRequired(); String name = mService.getFieldDetectionServiceName(userId); - boolean enabled = !TextUtils.isEmpty(name); + boolean pccFlagEnabled = mService.isPccClassificationFlagEnabled(); + boolean enabled = (!TextUtils.isEmpty(name)) && pccFlagEnabled; pw.println(enabled); return 0; } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 1098ce644c07..b6baf7e89a5d 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -2863,8 +2863,7 @@ public class UserBackupManagerService { if (DEBUG) { Slog.d( TAG, - addUserIdToLogMessage( - mUserId, "Starting backup confirmation UI, token=" + token)); + addUserIdToLogMessage(mUserId, "Starting backup confirmation UI")); } if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) { Slog.e( diff --git a/services/backup/lint-baseline.xml b/services/backup/lint-baseline.xml index 28bb937cfd9c..93c9390feb9c 100644 --- a/services/backup/lint-baseline.xml +++ b/services/backup/lint-baseline.xml @@ -12,4 +12,28 @@ column="16"/> </issue> + <issue + id="SimpleManualPermissionEnforcement" + message="IRestoreSession permission check should be converted to @EnforcePermission annotation"> + <location + file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java" + line="88"/> + </issue> + + <issue + id="SimpleManualPermissionEnforcement" + message="IRestoreSession permission check should be converted to @EnforcePermission annotation"> + <location + file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java" + line="144"/> + </issue> + + <issue + id="SimpleManualPermissionEnforcement" + message="IRestoreSession permission check should be converted to @EnforcePermission annotation"> + <location + file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java" + line="207"/> + </issue> + </issues> diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java new file mode 100644 index 000000000000..715cf9a8807e --- /dev/null +++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java @@ -0,0 +1,111 @@ +/* + * 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.contentprotection; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageInfo; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Manages whether the content protection is enabled for an app using a blocklist. + * + * @hide + */ +class ContentProtectionBlocklistManager { + + private static final String TAG = "ContentProtectionBlocklistManager"; + + private static final String PACKAGE_NAME_BLOCKLIST_FILENAME = + "/product/etc/res/raw/content_protection/package_name_blocklist.txt"; + + @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager; + + @Nullable private Set<String> mPackageNameBlocklist; + + protected ContentProtectionBlocklistManager( + @NonNull ContentProtectionPackageManager contentProtectionPackageManager) { + mContentProtectionPackageManager = contentProtectionPackageManager; + } + + public boolean isAllowed(@NonNull String packageName) { + if (mPackageNameBlocklist == null) { + // List not loaded or failed to load, don't run on anything + return false; + } + if (mPackageNameBlocklist.contains(packageName)) { + return false; + } + PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName); + if (packageInfo == null) { + return false; + } + if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) { + return false; + } + if (mContentProtectionPackageManager.isSystemApp(packageInfo)) { + return false; + } + if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) { + return false; + } + return true; + } + + public void updateBlocklist(int blocklistSize) { + Slog.i(TAG, "Blocklist size updating to: " + blocklistSize); + mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize); + } + + @Nullable + private Set<String> readPackageNameBlocklist(int blocklistSize) { + if (blocklistSize <= 0) { + // Explicitly requested an empty blocklist + return Collections.emptySet(); + } + List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME); + if (lines == null) { + return null; + } + return lines.stream().limit(blocklistSize).collect(Collectors.toSet()); + } + + @VisibleForTesting + @Nullable + protected List<String> readLinesFromRawFile(@NonNull String filename) { + try (FileReader fileReader = new FileReader(filename); + BufferedReader bufferedReader = new BufferedReader(fileReader)) { + return bufferedReader + .lines() + .map(line -> line.trim()) + .filter(line -> !line.isBlank()) + .collect(Collectors.toList()); + } catch (Exception ex) { + Slog.e(TAG, "Failed to read: " + filename, ex); + return null; + } + } +} diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java index 5221468e125a..1847e5d708a3 100644 --- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java +++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java @@ -34,8 +34,9 @@ import java.util.Arrays; * * @hide */ -final class ContentProtectionPackageManager { - private static final String TAG = ContentProtectionPackageManager.class.getSimpleName(); +public class ContentProtectionPackageManager { + + private static final String TAG = "ContentProtectionPackageManager"; private static final PackageInfoFlags PACKAGE_INFO_FLAGS = PackageInfoFlags.of(PackageManager.GET_PERMISSIONS); @@ -51,7 +52,7 @@ final class ContentProtectionPackageManager { try { return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS); } catch (NameNotFoundException ex) { - Slog.w(TAG, "Package info not found: ", ex); + Slog.w(TAG, "Package info not found for: " + packageName, ex); return null; } } diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index e2fd37e7177a..9554e63b882b 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -36,6 +36,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.ExtconUEventObserver.ExtconInfo; import java.io.FileDescriptor; @@ -194,6 +195,8 @@ final class DockObserver extends SystemService { @Override public void onStart() { publishBinderService(TAG, new BinderService()); + // Logs dock state after setDockStateFromProviderLocked sets mReportedDockState + FrameworkStatsLog.write(FrameworkStatsLog.DOCK_STATE_CHANGED, mReportedDockState); } @Override @@ -255,7 +258,6 @@ final class DockObserver extends SystemService { + mReportedDockState); final int previousDockState = mPreviousDockState; mPreviousDockState = mReportedDockState; - // Skip the dock intent if not yet provisioned. final ContentResolver cr = getContext().getContentResolver(); if (!mDeviceProvisionedObserver.isDeviceProvisioned()) { diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index 123cd3288343..55130e4cbbe6 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -1,3 +1,6 @@ +# BootReceiver +per-file BootReceiver.java = gaillard@google.com + # Connectivity / Networking per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java,VpnManagerService.java = file:/services/core/java/com/android/server/net/OWNERS diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b9e1bd033706..aac4e7f75f81 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -8298,8 +8298,8 @@ public final class ActiveServices { r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT, 0 /* api_sate */, - 0 /* api_type */, - 0 /* api_timestamp */, + null /* api_type */, + null /* api_timestamp */, mAm.getUidStateLocked(r.appInfo.uid), mAm.getUidProcessCapabilityLocked(r.appInfo.uid), mAm.getUidStateLocked(r.mRecentCallingUid), diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 030d596a1676..8c1fd516028e 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -292,6 +292,15 @@ public class BroadcastConstants { private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active"; private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = true; + /** + * For {@link BroadcastQueueModernImpl}: How frequently we should check for the pending + * cold start validity. + */ + public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30 * 1000; + private static final String KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS = + "pending_cold_start_check_interval_millis"; + private static final long DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30_000; + // Settings override tracking for this instance private String mSettingsKey; private SettingsObserver mSettingsObserver; @@ -441,6 +450,9 @@ public class BroadcastConstants { DEFAULT_MAX_HISTORY_SUMMARY_SIZE); CORE_DEFER_UNTIL_ACTIVE = getDeviceConfigBoolean(KEY_CORE_DEFER_UNTIL_ACTIVE, DEFAULT_CORE_DEFER_UNTIL_ACTIVE); + PENDING_COLD_START_CHECK_INTERVAL_MILLIS = getDeviceConfigLong( + KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS, + DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS); } // TODO: migrate BroadcastRecord to accept a BroadcastConstants @@ -499,6 +511,8 @@ public class BroadcastConstants { MAX_CONSECUTIVE_NORMAL_DISPATCHES).println(); pw.print(KEY_CORE_DEFER_UNTIL_ACTIVE, CORE_DEFER_UNTIL_ACTIVE).println(); + pw.print(KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS, + PENDING_COLD_START_CHECK_INTERVAL_MILLIS).println(); pw.decreaseIndent(); pw.println(); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 2a8c3787e58e..9e4e87975a28 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -248,6 +248,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_DELIVERY_TIMEOUT_HARD = 3; private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4; private static final int MSG_CHECK_HEALTH = 5; + private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6; private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); @@ -284,6 +285,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { checkHealth(); return true; } + case MSG_CHECK_PENDING_COLD_START_VALIDITY: { + checkPendingColdStartValidity(); + return true; + } } return false; }; @@ -450,10 +455,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // skip to look for another warm process if (mRunningColdStart == null) { mRunningColdStart = queue; - } else { + } else if (isPendingColdStartValid()) { // Move to considering next runnable queue queue = nextQueue; continue; + } else { + // Pending cold start is not valid, so clear it and move on. + clearInvalidPendingColdStart(); + mRunningColdStart = queue; } } @@ -486,11 +495,46 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER); } + checkPendingColdStartValidity(); checkAndRemoveWaitingFor(); traceEnd(cookie); } + private boolean isPendingColdStartValid() { + if (mRunningColdStart.app.getPid() > 0) { + // If the process has already started, check if it wasn't killed. + return !mRunningColdStart.app.isKilled(); + } else { + // Otherwise, check if the process start is still pending. + return mRunningColdStart.app.isPendingStart(); + } + } + + private void clearInvalidPendingColdStart() { + logw("Clearing invalid pending cold start: " + mRunningColdStart); + onApplicationCleanupLocked(mRunningColdStart.app); + } + + private void checkPendingColdStartValidity() { + // There are a few cases where a starting process gets killed but AMS doesn't report + // this event. So, once we start waiting for a pending cold start, periodically check + // if the pending start is still valid and if not, clear it so that the queue doesn't + // keep waiting for the process start forever. + synchronized (mService) { + // If there is no pending cold start, then nothing to do. + if (mRunningColdStart == null) { + return; + } + if (isPendingColdStartValid()) { + mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_PENDING_COLD_START_VALIDITY, + mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS); + } else { + clearInvalidPendingColdStart(); + } + } + } + @Override public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) { // Process records can be recycled, so always start by looking up the diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java index daa4ba4f1e16..9b3f24933f02 100644 --- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java +++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java @@ -451,6 +451,10 @@ public class ForegroundServiceTypeLoggerModule { @ForegroundServiceApiType int apiType, long timestamp) { final long apiDurationBeforeFgsStart = r.mFgsEnterTime - timestamp; final long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime; + final int[] apiTypes = new int[1]; + apiTypes[0] = apiType; + final long[] timeStamps = new long[1]; + timeStamps[0] = timestamp; FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortInstanceName, @@ -475,8 +479,8 @@ public class ForegroundServiceTypeLoggerModule { r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT, apiState, - apiType, - timestamp, + apiTypes, + timeStamps, ActivityManager.PROCESS_STATE_UNKNOWN, ActivityManager.PROCESS_CAPABILITY_NONE, ActivityManager.PROCESS_STATE_UNKNOWN, @@ -500,6 +504,10 @@ public class ForegroundServiceTypeLoggerModule { apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType); } } + final int[] apiTypes = new int[1]; + apiTypes[0] = apiType; + final long[] timeStamps = new long[1]; + timeStamps[0] = timestamp; FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, uid, null, @@ -522,8 +530,8 @@ public class ForegroundServiceTypeLoggerModule { 0, 0, apiState, - apiType, - timestamp, + apiTypes, + timeStamps, ActivityManager.PROCESS_STATE_UNKNOWN, ActivityManager.PROCESS_CAPABILITY_NONE, ActivityManager.PROCESS_STATE_UNKNOWN, diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 59904efb53e1..4eaee4aa7b79 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -348,21 +348,22 @@ public final class PendingIntentRecord extends IIntentSender.Stub { * use caller's BAL permission. */ public static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller( - @Nullable ActivityOptions activityOptions, int callingUid) { + @Nullable ActivityOptions activityOptions, int callingUid, + @Nullable String callingPackage) { if (activityOptions == null) { // since the ActivityOptions were not created by the app itself, determine the default // for the app - return getDefaultBackgroundStartPrivileges(callingUid); + return getDefaultBackgroundStartPrivileges(callingUid, callingPackage); } return getBackgroundStartPrivilegesAllowedByCaller(activityOptions.toBundle(), - callingUid); + callingUid, callingPackage); } private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller( - @Nullable Bundle options, int callingUid) { + @Nullable Bundle options, int callingUid, @Nullable String callingPackage) { if (options == null || !options.containsKey( ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) { - return getDefaultBackgroundStartPrivileges(callingUid); + return getDefaultBackgroundStartPrivileges(callingUid, callingPackage); } return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED) ? BackgroundStartPrivileges.ALLOW_BAL @@ -381,7 +382,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { android.Manifest.permission.LOG_COMPAT_CHANGE }) public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges( - int callingUid) { + int callingUid, @Nullable String callingPackage) { if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { // We temporarily allow BAL for system processes, while we verify that all valid use // cases are opted in explicitly to grant their BAL permission. @@ -390,7 +391,9 @@ public final class PendingIntentRecord extends IIntentSender.Stub { // as soon as that app is upgraded (or removed) BAL would be blocked. (b/283138430) return BackgroundStartPrivileges.ALLOW_BAL; } - boolean isChangeEnabledForApp = CompatChanges.isChangeEnabled( + boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled( + DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage, + UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled( DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingUid); if (isChangeEnabledForApp) { return BackgroundStartPrivileges.ALLOW_FGS; @@ -646,7 +649,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { // temporarily allow receivers and services to open activities from background if the // PendingIntent.send() caller was foreground at the time of sendInner() call if (uid != callingUid && controller.mAtmInternal.isUidForeground(callingUid)) { - return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid); + return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid, null); } return BackgroundStartPrivileges.NONE; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c59c2802dd67..0c7282bf3be1 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -45,7 +45,6 @@ import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; -import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.BroadcastOptions; @@ -167,7 +166,6 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorManager; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.System; import android.service.notification.ZenModeConfig; @@ -187,10 +185,8 @@ import android.view.KeyEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; - import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -252,7 +248,6 @@ public class AudioService extends IAudioService.Stub AudioSystemAdapter.OnVolRangeInitRequestListener { private static final String TAG = "AS.AudioService"; - private static final boolean CONFIG_DEFAULT_VAL = false; private final AudioSystemAdapter mAudioSystem; private final SystemServerAdapter mSystemServer; @@ -309,7 +304,7 @@ public class AudioService extends IAudioService.Stub * indicates whether STREAM_NOTIFICATION is aliased to STREAM_RING * not final due to test method, see {@link #setNotifAliasRingForTest(boolean)}. */ - private boolean mNotifAliasRing; + private boolean mNotifAliasRing = false; /** * Test method to temporarily override whether STREAM_NOTIFICATION is aliased to STREAM_RING, @@ -1057,13 +1052,6 @@ public class AudioService extends IAudioService.Stub mUseVolumeGroupAliases = mContext.getResources().getBoolean( com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups); - mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); - - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - ActivityThread.currentApplication().getMainExecutor(), - this::onDeviceConfigChange); - // Initialize volume // Priority 1 - Android Property // Priority 2 - Audio Policy Service @@ -1157,6 +1145,11 @@ public class AudioService extends IAudioService.Stub MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM]; } + int minAssistantVolume = SystemProperties.getInt("ro.config.assistant_vol_min", -1); + if (minAssistantVolume != -1) { + MIN_STREAM_VOLUME[AudioSystem.STREAM_ASSISTANT] = minAssistantVolume; + } + // Read following properties to configure max volume (number of steps) and default volume // for STREAM_NOTIFICATION and STREAM_RING: // config_audio_notif_vol_default @@ -1277,22 +1270,6 @@ public class AudioService extends IAudioService.Stub } /** - * Separating notification volume from ring is NOT of aliasing the corresponding streams - * @param properties - */ - private void onDeviceConfigChange(DeviceConfig.Properties properties) { - Set<String> changeSet = properties.getKeyset(); - if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) { - boolean newNotifAliasRing = !properties.getBoolean( - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); - if (mNotifAliasRing != newNotifAliasRing) { - mNotifAliasRing = newNotifAliasRing; - updateStreamVolumeAlias(true, TAG); - } - } - } - - /** * Called by handling of MSG_INIT_STREAMS_VOLUMES */ private void onInitStreamsAndVolumes() { diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index bf5e8ee27157..1989bc77583a 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -21,8 +21,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE; -import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; -import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED; @@ -44,7 +42,6 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; -import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -68,7 +65,6 @@ import com.android.server.biometrics.log.OperationContextExt; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Collection; import java.util.List; import java.util.Random; import java.util.function.Function; @@ -134,7 +130,6 @@ public final class AuthSession implements IBinder.DeathRecipient { // The current state, which can be either idle, called, or started private @SessionState int mState = STATE_AUTH_IDLE; - private @BiometricMultiSensorMode int mMultiSensorMode; private int[] mSensors; // TODO(b/197265902): merge into state private boolean mCancelled; @@ -255,7 +250,6 @@ public final class AuthSession implements IBinder.DeathRecipient { // SystemUI invokes that path. mState = STATE_SHOWING_DEVICE_CREDENTIAL; mSensors = new int[0]; - mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT; mStatusBarService.showAuthenticationDialog( mPromptInfo, @@ -266,8 +260,7 @@ public final class AuthSession implements IBinder.DeathRecipient { mUserId, mOperationId, mOpPackageName, - mRequestId, - mMultiSensorMode); + mRequestId); } else if (!mPreAuthInfo.eligibleSensors.isEmpty()) { // Some combination of biometric or biometric|credential is requested setSensorsToStateWaitingForCookie(false /* isTryAgain */); @@ -310,8 +303,6 @@ public final class AuthSession implements IBinder.DeathRecipient { for (int i = 0; i < mPreAuthInfo.eligibleSensors.size(); i++) { mSensors[i] = mPreAuthInfo.eligibleSensors.get(i).id; } - mMultiSensorMode = getMultiSensorModeForNewSession( - mPreAuthInfo.eligibleSensors); mStatusBarService.showAuthenticationDialog(mPromptInfo, mSysuiReceiver, @@ -321,8 +312,7 @@ public final class AuthSession implements IBinder.DeathRecipient { mUserId, mOperationId, mOpPackageName, - mRequestId, - mMultiSensorMode); + mRequestId); mState = STATE_AUTH_STARTED; } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); @@ -438,7 +428,6 @@ public final class AuthSession implements IBinder.DeathRecipient { mPromptInfo.setAuthenticators(authenticators); mState = STATE_SHOWING_DEVICE_CREDENTIAL; - mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT; mSensors = new int[0]; mStatusBarService.showAuthenticationDialog( @@ -450,8 +439,7 @@ public final class AuthSession implements IBinder.DeathRecipient { mUserId, mOperationId, mOpPackageName, - mRequestId, - mMultiSensorMode); + mRequestId); } else { mClientReceiver.onError(modality, error, vendorCode); return true; @@ -545,13 +533,30 @@ public final class AuthSession implements IBinder.DeathRecipient { } } - void onDialogAnimatedIn() { + void onDialogAnimatedIn(boolean startFingerprintNow) { if (mState != STATE_AUTH_STARTED) { Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState); return; } mState = STATE_AUTH_STARTED_UI_SHOWING; + if (startFingerprintNow) { + startAllPreparedFingerprintSensors(); + } else { + Slog.d(TAG, "delaying fingerprint sensor start"); + } + } + + // call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the + // fingerprint sensor (i.e. face auth has failed or is not available) + void onStartFingerprint() { + if (mState != STATE_AUTH_STARTED + && mState != STATE_AUTH_STARTED_UI_SHOWING + && mState != STATE_AUTH_PAUSED + && mState != STATE_ERROR_PENDING_SYSUI) { + Slog.w(TAG, "onStartFingerprint, started from unexpected state: " + mState); + } + startAllPreparedFingerprintSensors(); } @@ -919,25 +924,6 @@ public final class AuthSession implements IBinder.DeathRecipient { } } - @BiometricMultiSensorMode - private static int getMultiSensorModeForNewSession(Collection<BiometricSensor> sensors) { - boolean hasFace = false; - boolean hasFingerprint = false; - - for (BiometricSensor sensor: sensors) { - if (sensor.modality == TYPE_FACE) { - hasFace = true; - } else if (sensor.modality == TYPE_FINGERPRINT) { - hasFingerprint = true; - } - } - - if (hasFace && hasFingerprint) { - return BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; - } - return BIOMETRIC_MULTI_SENSOR_DEFAULT; - } - @Override public String toString() { return "State: " + mState diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 448843477ecd..0942d8527565 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -480,8 +480,13 @@ public class BiometricService extends SystemService { } @Override - public void onDialogAnimatedIn() { - mHandler.post(() -> handleOnDialogAnimatedIn(requestId)); + public void onDialogAnimatedIn(boolean startFingerprintNow) { + mHandler.post(() -> handleOnDialogAnimatedIn(requestId, startFingerprintNow)); + } + + @Override + public void onStartFingerprintNow() { + mHandler.post(() -> handleOnStartFingerprintNow(requestId)); } }; } @@ -1237,7 +1242,7 @@ public class BiometricService extends SystemService { } } - private void handleOnDialogAnimatedIn(long requestId) { + private void handleOnDialogAnimatedIn(long requestId, boolean startFingerprintNow) { Slog.d(TAG, "handleOnDialogAnimatedIn"); final AuthSession session = getAuthSessionIfCurrent(requestId); @@ -1246,7 +1251,19 @@ public class BiometricService extends SystemService { return; } - session.onDialogAnimatedIn(); + session.onDialogAnimatedIn(startFingerprintNow); + } + + private void handleOnStartFingerprintNow(long requestId) { + Slog.d(TAG, "handleOnStartFingerprintNow"); + + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleOnStartFingerprintNow: AuthSession is not current"); + return; + } + + session.onStartFingerprint(); } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index a486d16189fa..694dfd28d0cc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -70,9 +70,11 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible // for that the queue will wait indefinitely until the field is cleared. - if (clientMonitor instanceof StopUserClient<?> && !success) { - Slog.w(getTag(), - "StopUserClient failed(), is the HAL stuck? Clearing mStopUserClient"); + if (clientMonitor instanceof StopUserClient<?>) { + if (!success) { + Slog.w(getTag(), "StopUserClient failed(), is the HAL stuck? " + + "Clearing mStopUserClient"); + } mStopUserClient = null; } if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index ffbf4e12f2ae..2ad41c2a7a02 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -89,7 +89,7 @@ public class Sensor { @NonNull private final Map<Integer, Long> mAuthenticatorIds; @NonNull private final Supplier<AidlSession> mLazySession; - @Nullable private AidlSession mCurrentSession; + @Nullable AidlSession mCurrentSession; @VisibleForTesting public static class HalSessionCallback extends ISessionCallback.Stub { @@ -486,7 +486,7 @@ public class Sensor { Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull BiometricContext biometricContext) { + @NonNull BiometricContext biometricContext, AidlSession session) { mTag = tag; mProvider = provider; mContext = context; @@ -549,6 +549,14 @@ public class Sensor { mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; } + Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, + @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull BiometricContext biometricContext) { + this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher, + biometricContext, null); + } + @NonNull Supplier<AidlSession> getLazySession() { return mLazySession; } @@ -557,7 +565,7 @@ public class Sensor { return mSensorProperties; } - @Nullable AidlSession getSessionForUser(int userId) { + @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) { if (mCurrentSession != null && mCurrentSession.getUserId() == userId) { return mCurrentSession; } else { @@ -641,6 +649,8 @@ public class Sensor { BiometricsProtoEnums.MODALITY_FACE, BiometricsProtoEnums.ISSUE_HAL_DEATH, -1 /* sensorId */); + } else if (client != null) { + client.cancel(); } mScheduler.recordCrashState(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index c0dde721b962..56b85ceb8e6b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -90,7 +90,7 @@ public class Sensor { @NonNull private final LockoutCache mLockoutCache; @NonNull private final Map<Integer, Long> mAuthenticatorIds; - @Nullable private AidlSession mCurrentSession; + @Nullable AidlSession mCurrentSession; @NonNull private final Supplier<AidlSession> mLazySession; @VisibleForTesting @@ -439,7 +439,7 @@ public class Sensor { @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - @NonNull BiometricContext biometricContext) { + @NonNull BiometricContext biometricContext, AidlSession session) { mTag = tag; mProvider = provider; mContext = context; @@ -501,6 +501,16 @@ public class Sensor { }); mAuthenticatorIds = new HashMap<>(); mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; + mCurrentSession = session; + } + + Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, + @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull BiometricContext biometricContext) { + this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher, + gestureAvailabilityDispatcher, biometricContext, null); } @NonNull Supplier<AidlSession> getLazySession() { @@ -599,6 +609,8 @@ public class Sensor { BiometricsProtoEnums.MODALITY_FINGERPRINT, BiometricsProtoEnums.ISSUE_HAL_DEATH, -1 /* sensorId */); + } else if (client != null) { + client.cancel(); } mScheduler.recordCrashState(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index ca1abd683d4f..7d0d5a73f093 100755..100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -1126,6 +1126,16 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { removeActionExcept(clazz, null); } + // Remove all running actions. + @ServiceThreadOnly + void removeAllActions() { + assertRunOnServiceThread(); + for (HdmiCecFeatureAction action : mActions) { + action.finish(false); + } + mActions.clear(); + } + // Remove all actions matched with the given Class type besides |exception|. @ServiceThreadOnly <T extends HdmiCecFeatureAction> void removeActionExcept( diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index a026c4b59ec5..184bdd778300 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -231,6 +231,14 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); mService.unregisterTvInputCallback(mTvInputCallback); + // Removing actions and invoking the callback is similar to + // HdmiCecLocalDevicePlayback#disableDevice and HdmiCecLocalDeviceTv#disableDevice, + // with the difference that in those classes only specific actions are removed and + // here we remove all actions. We don't expect any issues with removing all actions + // at this time, but we have to pay attention in the future. + removeAllActions(); + // Call the callback instantly or else it will be called 5 seconds later. + checkIfPendingActionsCleared(); } @Override diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index dd974d1c5ea6..d2266e3a367b 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -889,22 +889,31 @@ public class LockSettingsService extends ILockSettings.Stub { } - private void migrateOldDataAfterSystemReady() { - // Migrate the FRP credential to the persistent data block + @VisibleForTesting + void migrateOldDataAfterSystemReady() { + // Write the FRP persistent data block if needed. + // + // The original purpose of this code was to write the FRP block for the first time, when + // upgrading from Android 8.1 or earlier which didn't use the FRP block. This code has + // since been repurposed to also fix the "bad" (non-forwards-compatible) FRP block written + // by Android 14 Beta 2. For this reason, the database key used here has been renamed from + // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices + // where it had run before. if (LockPatternUtils.frpCredentialEnabled(mContext) - && !getBoolean("migrated_frp", false, 0)) { + && !getBoolean("migrated_frp2", false, 0)) { migrateFrpCredential(); - setBoolean("migrated_frp", true, 0); + setBoolean("migrated_frp2", true, 0); } } /** - * Migrate the credential for the FRP credential owner user if the following are satisfied: - * - the user has a secure credential - * - the FRP credential is not set up + * Write the FRP persistent data block if the following are satisfied: + * - the user who owns the FRP credential has a nonempty credential + * - the FRP persistent data block doesn't exist or uses the "bad" format from Android 14 Beta 2 */ private void migrateFrpCredential() { - if (mStorage.readPersistentDataBlock() != PersistentData.NONE) { + PersistentData data = mStorage.readPersistentDataBlock(); + if (data != PersistentData.NONE && !data.isBadFormatFromAndroid14Beta()) { return; } for (UserInfo userInfo : mUserManager.getUsers()) { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 731ecadc1372..2fa637e030f1 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -606,6 +606,11 @@ class LockSettingsStorage { this.payload = payload; } + public boolean isBadFormatFromAndroid14Beta() { + return (this.type == TYPE_SP_GATEKEEPER || this.type == TYPE_SP_WEAVER) + && SyntheticPasswordManager.PasswordData.isBadFormatFromAndroid14Beta(this.payload); + } + public static PersistentData fromBytes(byte[] frpData) { if (frpData == null || frpData.length == 0) { return NONE; diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 8b8c5f600255..e8fd6f88359c 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -152,9 +152,6 @@ class SyntheticPasswordManager { // The security strength of the synthetic password, in bytes private static final int SYNTHETIC_PASSWORD_SECURITY_STRENGTH = 256 / 8; - public static final short PASSWORD_DATA_V1 = 1; - public static final short PASSWORD_DATA_V2 = 2; - private static final int PASSWORD_SCRYPT_LOG_N = 11; private static final int PASSWORD_SCRYPT_LOG_R = 3; private static final int PASSWORD_SCRYPT_LOG_P = 1; @@ -373,27 +370,33 @@ class SyntheticPasswordManager { return result; } + /** + * Returns true if the given serialized PasswordData begins with the value 2 as a short. + * This detects the "bad" (non-forwards-compatible) PasswordData format that was temporarily + * used during development of Android 14. For more details, see fromBytes() below. + */ + public static boolean isBadFormatFromAndroid14Beta(byte[] data) { + return data != null && data.length >= 2 && data[0] == 0 && data[1] == 2; + } + public static PasswordData fromBytes(byte[] data) { PasswordData result = new PasswordData(); ByteBuffer buffer = ByteBuffer.allocate(data.length); buffer.put(data, 0, data.length); buffer.flip(); - /* - * Originally this file did not contain a version number. However, its first field was - * 'credentialType' as an 'int'. Since 'credentialType' could only be in the range - * [-1, 4] and this file uses big endian byte order, the first two bytes were redundant, - * and when interpreted as a 'short' could only contain -1 or 0. Therefore, we've now - * reclaimed these two bytes for a 'short' version number and shrunk 'credentialType' - * to a 'short'. - */ - short version = buffer.getShort(); - if (version == ((short) 0) || version == (short) -1) { - version = PASSWORD_DATA_V1; - } else if (version != PASSWORD_DATA_V2) { - throw new IllegalArgumentException("Unknown PasswordData version: " + version); - } - result.credentialType = buffer.getShort(); + /* + * The serialized PasswordData is supposed to begin with credentialType as an int. + * However, all credentialType values fit in a short and the byte order is big endian, + * so the first two bytes don't convey any non-redundant information. For this reason, + * temporarily during development of Android 14, the first two bytes were "stolen" from + * credentialType to use for a data format version number. + * + * However, this change was reverted as it was a non-forwards-compatible change. (See + * toBytes() for why this data format needs to be forwards-compatible.) Therefore, + * recover from this misstep by ignoring the first two bytes. + */ + result.credentialType = (short) buffer.getInt(); result.scryptLogN = buffer.get(); result.scryptLogR = buffer.get(); result.scryptLogP = buffer.get(); @@ -407,7 +410,7 @@ class SyntheticPasswordManager { } else { result.passwordHandle = null; } - if (version == PASSWORD_DATA_V2) { + if (buffer.remaining() >= Integer.BYTES) { result.pinLength = buffer.getInt(); } else { result.pinLength = PIN_LENGTH_UNAVAILABLE; @@ -415,16 +418,25 @@ class SyntheticPasswordManager { return result; } + /** + * Serializes this PasswordData into a byte array. + * <p> + * Careful: all changes to the format of the serialized PasswordData must be forwards + * compatible. I.e., older versions of Android must still accept the latest PasswordData. + * This is because a serialized PasswordData is stored in the Factory Reset Protection (FRP) + * persistent data block. It's possible that a device has FRP set up on a newer version of + * Android, is factory reset, and then is set up with an older version of Android. + */ public byte[] toBytes() { - ByteBuffer buffer = ByteBuffer.allocate(2 * Short.BYTES + 3 * Byte.BYTES + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES + Integer.BYTES + salt.length + Integer.BYTES + (passwordHandle != null ? passwordHandle.length : 0) + Integer.BYTES); + // credentialType must fit in a short. For an explanation, see fromBytes(). if (credentialType < Short.MIN_VALUE || credentialType > Short.MAX_VALUE) { throw new IllegalArgumentException("Unknown credential type: " + credentialType); } - buffer.putShort(PASSWORD_DATA_V2); - buffer.putShort((short) credentialType); + buffer.putInt(credentialType); buffer.put(scryptLogN); buffer.put(scryptLogR); buffer.put(scryptLogP); @@ -1564,8 +1576,10 @@ class SyntheticPasswordManager { } return result; } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { + Slog.e(TAG, "Gatekeeper verification of synthetic password failed with RESPONSE_RETRY"); return VerifyCredentialResponse.fromTimeout(response.getTimeout()); } else { + Slog.e(TAG, "Gatekeeper verification of synthetic password failed with RESPONSE_ERROR"); return VerifyCredentialResponse.ERROR; } } diff --git a/services/core/java/com/android/server/os/OWNERS b/services/core/java/com/android/server/os/OWNERS index 19573323e5ad..70be161ba9a8 100644 --- a/services/core/java/com/android/server/os/OWNERS +++ b/services/core/java/com/android/server/os/OWNERS @@ -1,2 +1,5 @@ # Bugreporting per-file Bugreport* = file:/platform/frameworks/native:/cmds/dumpstate/OWNERS + +# NativeTombstone +per-file NativeTombstone* = gaillard@google.com diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 06db5be349d4..4f00dc37caa6 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2140,7 +2140,7 @@ final class InstallPackageHelper { private void updateSettingsInternalLI(AndroidPackage pkg, int[] allUsers, InstallRequest installRequest) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettingsInternal"); final String pkgName = pkg.getPackageName(); final int[] installedForUsers = installRequest.getOriginUsers(); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 395c5d5913b8..d855ac034db7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -216,6 +216,9 @@ class PackageManagerShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { switch (cmd) { + case "help": + onHelp(); + return 0; case "path": return runPath(); case "dump": diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 7a5664f8135e..5288e85aa490 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -37,6 +37,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PackageTagsList; import android.os.Process; +import android.os.SystemProperties; import android.os.UserHandle; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; @@ -68,6 +69,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat private static final String ACTIVITY_RECOGNITION_TAGS = "android:activity_recognition_allow_listed_tags"; private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";"; + private static final boolean SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED = + SystemProperties.getBoolean("ro.hotword.detection_service_required", false); @NonNull private final Object mLock = new Object(); @@ -199,10 +202,16 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } } - private static boolean isHotwordDetectionServiceRequired(PackageManager pm) { + /** + * @hide + */ + public static boolean isHotwordDetectionServiceRequired(PackageManager pm) { // The HotwordDetectionService APIs aren't ready yet for Auto or TV. - return !(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) - || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)); + if (pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { + return false; + } + return SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED; } @Override diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 701e9212aad8..b0fe628c513d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2204,6 +2204,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_shortPressOnSleepBehavior); mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean( com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup); + mShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior); + mLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior); + mDoublePressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_doublePressOnStemPrimaryBehavior); + mTriplePressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_triplePressOnStemPrimaryBehavior); mHapticTextHandleEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); @@ -2610,14 +2618,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE; } - mShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger( - com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior); - mLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger( - com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior); - mDoublePressOnStemPrimaryBehavior = mContext.getResources().getInteger( - com.android.internal.R.integer.config_doublePressOnStemPrimaryBehavior); - mTriplePressOnStemPrimaryBehavior = mContext.getResources().getInteger( - com.android.internal.R.integer.config_triplePressOnStemPrimaryBehavior); } public void updateSettings() { diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 4a57592aa1ae..fae6262207d5 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -251,10 +251,11 @@ public class BatteryStatsImpl extends BatteryStats { private static final LongCounter[] ZERO_LONG_COUNTER_ARRAY = new LongCounter[]{ZERO_LONG_COUNTER}; - private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); @VisibleForTesting + protected KernelWakelockReader mKernelWakelockReader; + @VisibleForTesting protected KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader; @VisibleForTesting protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders; @@ -1763,6 +1764,7 @@ public class BatteryStatsImpl extends BatteryStats { mCpuUidFreqTimeReader = new KernelCpuUidFreqTimeReader(true, clock); mCpuUidActiveTimeReader = new KernelCpuUidActiveTimeReader(true, clock); mCpuUidClusterTimeReader = new KernelCpuUidClusterTimeReader(true, clock); + mKernelWakelockReader = new KernelWakelockReader(); } /** @@ -2675,19 +2677,18 @@ public class BatteryStatsImpl extends BatteryStats { * The reported count from /proc/wakelocks when unplug() was last * called. */ - int mUnpluggedReportedCount; + int mBaseReportedCount; /** * The most recent reported total_time from /proc/wakelocks. */ long mCurrentReportedTotalTimeUs; - /** * The reported total_time from /proc/wakelocks when unplug() was last * called. */ - long mUnpluggedReportedTotalTimeUs; + long mBaseReportedTotalTimeUs; /** * Whether we are currently in a discharge cycle. @@ -2708,9 +2709,9 @@ public class BatteryStatsImpl extends BatteryStats { public SamplingTimer(Clock clock, TimeBase timeBase, Parcel in) { super(clock, 0, timeBase, in); mCurrentReportedCount = in.readInt(); - mUnpluggedReportedCount = in.readInt(); + mBaseReportedCount = in.readInt(); mCurrentReportedTotalTimeUs = in.readLong(); - mUnpluggedReportedTotalTimeUs = in.readLong(); + mBaseReportedTotalTimeUs = in.readLong(); mTrackingReportedValues = in.readInt() == 1; mTimeBaseRunning = timeBase.isRunning(); } @@ -2736,8 +2737,8 @@ public class BatteryStatsImpl extends BatteryStats { public void endSample(long elapsedRealtimeUs) { mTotalTimeUs = computeRunTimeLocked(0 /* unused by us */, elapsedRealtimeUs); mCount = computeCurrentCountLocked(); - mUnpluggedReportedTotalTimeUs = mCurrentReportedTotalTimeUs = 0; - mUnpluggedReportedCount = mCurrentReportedCount = 0; + mBaseReportedTotalTimeUs = mCurrentReportedTotalTimeUs = 0; + mBaseReportedCount = mCurrentReportedCount = 0; mTrackingReportedValues = false; } @@ -2762,10 +2763,21 @@ public class BatteryStatsImpl extends BatteryStats { * @param count total number of times the event being sampled occurred. */ public void update(long totalTimeUs, int count, long elapsedRealtimeUs) { + update(totalTimeUs, 0, count, elapsedRealtimeUs); + } + + /** + * Updates the current recorded values. See {@link #update(long, int, long)} + * + * @param activeTimeUs Time that the currently active wake lock has been held. + */ + public void update(long totalTimeUs, long activeTimeUs, int count, + long elapsedRealtimeUs) { if (mTimeBaseRunning && !mTrackingReportedValues) { - // Updating the reported value for the first time. - mUnpluggedReportedTotalTimeUs = totalTimeUs; - mUnpluggedReportedCount = count; + // Updating the reported value for the first time. If the wake lock is currently + // active, mark the time it was acquired as the base timestamp. + mBaseReportedTotalTimeUs = totalTimeUs - activeTimeUs; + mBaseReportedCount = activeTimeUs == 0 ? count : count - 1; } mTrackingReportedValues = true; @@ -2800,8 +2812,8 @@ public class BatteryStatsImpl extends BatteryStats { public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) { super.onTimeStarted(elapsedRealtimeUs, baseUptimeUs, baseRealtimeUs); if (mTrackingReportedValues) { - mUnpluggedReportedTotalTimeUs = mCurrentReportedTotalTimeUs; - mUnpluggedReportedCount = mCurrentReportedCount; + mBaseReportedTotalTimeUs = mCurrentReportedTotalTimeUs; + mBaseReportedCount = mCurrentReportedCount; } mTimeBaseRunning = true; } @@ -2816,30 +2828,30 @@ public class BatteryStatsImpl extends BatteryStats { public void logState(Printer pw, String prefix) { super.logState(pw, prefix); pw.println(prefix + "mCurrentReportedCount=" + mCurrentReportedCount - + " mUnpluggedReportedCount=" + mUnpluggedReportedCount + + " mBaseReportedCount=" + mBaseReportedCount + " mCurrentReportedTotalTime=" + mCurrentReportedTotalTimeUs - + " mUnpluggedReportedTotalTime=" + mUnpluggedReportedTotalTimeUs); + + " mBaseReportedTotalTimeUs=" + mBaseReportedTotalTimeUs); } @Override protected long computeRunTimeLocked(long curBatteryRealtime, long elapsedRealtimeUs) { return mTotalTimeUs + (mTimeBaseRunning && mTrackingReportedValues - ? mCurrentReportedTotalTimeUs - mUnpluggedReportedTotalTimeUs : 0); + ? mCurrentReportedTotalTimeUs - mBaseReportedTotalTimeUs : 0); } @Override protected int computeCurrentCountLocked() { return mCount + (mTimeBaseRunning && mTrackingReportedValues - ? mCurrentReportedCount - mUnpluggedReportedCount : 0); + ? mCurrentReportedCount - mBaseReportedCount : 0); } @Override public void writeToParcel(Parcel out, long elapsedRealtimeUs) { super.writeToParcel(out, elapsedRealtimeUs); out.writeInt(mCurrentReportedCount); - out.writeInt(mUnpluggedReportedCount); + out.writeInt(mBaseReportedCount); out.writeLong(mCurrentReportedTotalTimeUs); - out.writeLong(mUnpluggedReportedTotalTimeUs); + out.writeLong(mBaseReportedTotalTimeUs); out.writeInt(mTrackingReportedValues ? 1 : 0); } @@ -2847,8 +2859,8 @@ public class BatteryStatsImpl extends BatteryStats { public boolean reset(boolean detachIfReset, long elapsedRealtimeUs) { super.reset(detachIfReset, elapsedRealtimeUs); mTrackingReportedValues = false; - mUnpluggedReportedTotalTimeUs = 0; - mUnpluggedReportedCount = 0; + mBaseReportedTotalTimeUs = 0; + mBaseReportedCount = 0; return true; } } @@ -13398,6 +13410,10 @@ public class BatteryStatsImpl extends BatteryStats { * Read and distribute kernel wake lock use across apps. */ public void updateKernelWakelocksLocked(long elapsedRealtimeUs) { + if (mKernelWakelockReader == null) { + return; + } + final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats( mTmpWakelockStats); if (wakelockStats == null) { @@ -13416,8 +13432,8 @@ public class BatteryStatsImpl extends BatteryStats { mKernelWakelockStats.put(name, kwlt); } - kwlt.update(kws.mTotalTime, kws.mCount, elapsedRealtimeUs); - kwlt.setUpdateVersion(kws.mVersion); + kwlt.update(kws.totalTimeUs, kws.activeTimeUs, kws.count, elapsedRealtimeUs); + kwlt.setUpdateVersion(kws.version); } int numWakelocksSetStale = 0; @@ -13471,7 +13487,7 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, String.format("Added entry %d and updated timer to: " + "mUnpluggedReportedTotalTimeUs %d size %d", bandwidthEntries.keyAt(i), mKernelMemoryStats.get( - bandwidthEntries.keyAt(i)).mUnpluggedReportedTotalTimeUs, + bandwidthEntries.keyAt(i)).mBaseReportedTotalTimeUs, mKernelMemoryStats.size())); } } diff --git a/services/core/java/com/android/server/power/stats/KernelWakelockReader.java b/services/core/java/com/android/server/power/stats/KernelWakelockReader.java index 0377d8a0f23d..3fffcf713c31 100644 --- a/services/core/java/com/android/server/power/stats/KernelWakelockReader.java +++ b/services/core/java/com/android/server/power/stats/KernelWakelockReader.java @@ -42,30 +42,31 @@ public class KernelWakelockReader { private static final String sWakeupSourceFile = "/d/wakeup_sources"; private static final String sSysClassWakeupDir = "/sys/class/wakeup"; - private static final int[] PROC_WAKELOCKS_FORMAT = new int[] { - Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name - Process.PROC_QUOTES, - Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 1: count - Process.PROC_TAB_TERM, - Process.PROC_TAB_TERM, - Process.PROC_TAB_TERM, - Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime + private static final int[] PROC_WAKELOCKS_FORMAT = new int[]{ + Process.PROC_TAB_TERM | Process.PROC_OUT_STRING // 0: name + | Process.PROC_QUOTES, + Process.PROC_TAB_TERM | Process.PROC_OUT_LONG, // 1: count + Process.PROC_TAB_TERM, + Process.PROC_TAB_TERM | Process.PROC_OUT_LONG, // 3: activeSince + Process.PROC_TAB_TERM, + Process.PROC_TAB_TERM | Process.PROC_OUT_LONG, // 5: totalTime }; - private static final int[] WAKEUP_SOURCES_FORMAT = new int[] { - Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name - Process.PROC_TAB_TERM|Process.PROC_COMBINE| - Process.PROC_OUT_LONG, // 1: count - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE - |Process.PROC_OUT_LONG, // 6: totalTime + private static final int[] WAKEUP_SOURCES_FORMAT = new int[]{ + Process.PROC_TAB_TERM | Process.PROC_OUT_STRING, // 0: name + Process.PROC_TAB_TERM | Process.PROC_COMBINE + | Process.PROC_OUT_LONG, // 1: count + Process.PROC_TAB_TERM | Process.PROC_COMBINE, + Process.PROC_TAB_TERM | Process.PROC_COMBINE, + Process.PROC_TAB_TERM | Process.PROC_COMBINE, + Process.PROC_TAB_TERM | Process.PROC_COMBINE + | Process.PROC_OUT_LONG, // 5: activeSince + Process.PROC_TAB_TERM | Process.PROC_COMBINE + | Process.PROC_OUT_LONG, // 6: totalTime }; private final String[] mProcWakelocksName = new String[3]; - private final long[] mProcWakelocksData = new long[3]; + private final long[] mProcWakelocksData = new long[4]; private ISuspendControlServiceInternal mSuspendControlService = null; private byte[] mKernelWakelockBuffer = new byte[32 * 1024]; @@ -74,7 +75,7 @@ public class KernelWakelockReader { * @param staleStats Existing object to update. * @return the updated data. */ - public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) { + public KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) { boolean useSystemSuspend = (new File(sSysClassWakeupDir)).exists(); if (useSystemSuspend) { @@ -180,14 +181,16 @@ public class KernelWakelockReader { */ private KernelWakelockStats getWakelockStatsFromSystemSuspend( final KernelWakelockStats staleStats) { - WakeLockInfo[] wlStats = null; - try { - mSuspendControlService = waitForSuspendControlService(); - } catch (ServiceNotFoundException e) { - Slog.wtf(TAG, "Required service suspend_control not available", e); - return null; + if (mSuspendControlService == null) { + try { + mSuspendControlService = waitForSuspendControlService(); + } catch (ServiceNotFoundException e) { + Slog.wtf(TAG, "Required service suspend_control not available", e); + return null; + } } + WakeLockInfo[] wlStats; try { wlStats = mSuspendControlService.getWakeLockStats(); updateWakelockStats(wlStats, staleStats); @@ -210,13 +213,16 @@ public class KernelWakelockReader { for (WakeLockInfo info : wlStats) { if (!staleStats.containsKey(info.name)) { staleStats.put(info.name, new KernelWakelockStats.Entry((int) info.activeCount, - info.totalTime * 1000 /* ms to us */, sKernelWakelockUpdateVersion)); + info.totalTime * 1000 /* ms to us */, + info.isActive ? info.activeTime * 1000 : 0, + sKernelWakelockUpdateVersion)); } else { KernelWakelockStats.Entry kwlStats = staleStats.get(info.name); - kwlStats.mCount = (int) info.activeCount; + kwlStats.count = (int) info.activeCount; // Convert milliseconds to microseconds - kwlStats.mTotalTime = info.totalTime * 1000; - kwlStats.mVersion = sKernelWakelockUpdateVersion; + kwlStats.totalTimeUs = info.totalTime * 1000; + kwlStats.activeTimeUs = info.isActive ? info.activeTime * 1000 : 0; + kwlStats.version = sKernelWakelockUpdateVersion; } } @@ -232,6 +238,7 @@ public class KernelWakelockReader { String name; int count; long totalTime; + long activeTime; int startIndex; int endIndex; @@ -268,26 +275,30 @@ public class KernelWakelockReader { count = (int) wlData[1]; if (wakeup_sources) { - // convert milliseconds to microseconds - totalTime = wlData[2] * 1000; + // convert milliseconds to microseconds + activeTime = wlData[2] * 1000; + totalTime = wlData[3] * 1000; } else { - // convert nanoseconds to microseconds with rounding. - totalTime = (wlData[2] + 500) / 1000; + // convert nanoseconds to microseconds with rounding. + activeTime = (wlData[2] + 500) / 1000; + totalTime = (wlData[3] + 500) / 1000; } if (parsed && name.length() > 0) { if (!staleStats.containsKey(name)) { staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime, - sKernelWakelockUpdateVersion)); + activeTime, sKernelWakelockUpdateVersion)); } else { KernelWakelockStats.Entry kwlStats = staleStats.get(name); - if (kwlStats.mVersion == sKernelWakelockUpdateVersion) { - kwlStats.mCount += count; - kwlStats.mTotalTime += totalTime; + if (kwlStats.version == sKernelWakelockUpdateVersion) { + kwlStats.count += count; + kwlStats.totalTimeUs += totalTime; + kwlStats.activeTimeUs = activeTime; } else { - kwlStats.mCount = count; - kwlStats.mTotalTime = totalTime; - kwlStats.mVersion = sKernelWakelockUpdateVersion; + kwlStats.count = count; + kwlStats.totalTimeUs = totalTime; + kwlStats.activeTimeUs = activeTime; + kwlStats.version = sKernelWakelockUpdateVersion; } } } else if (!parsed) { @@ -326,7 +337,7 @@ public class KernelWakelockReader { public KernelWakelockStats removeOldStats(final KernelWakelockStats staleStats) { Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator(); while (itr.hasNext()) { - if (itr.next().mVersion != sKernelWakelockUpdateVersion) { + if (itr.next().version != sKernelWakelockUpdateVersion) { itr.remove(); } } diff --git a/services/core/java/com/android/server/power/stats/KernelWakelockStats.java b/services/core/java/com/android/server/power/stats/KernelWakelockStats.java index 502b7a008cb8..3ed3803fb5bc 100644 --- a/services/core/java/com/android/server/power/stats/KernelWakelockStats.java +++ b/services/core/java/com/android/server/power/stats/KernelWakelockStats.java @@ -22,14 +22,16 @@ import java.util.HashMap; */ public class KernelWakelockStats extends HashMap<String, KernelWakelockStats.Entry> { public static class Entry { - public int mCount; - public long mTotalTime; - public int mVersion; + public int count; + public long totalTimeUs; + public long activeTimeUs; + public int version; - Entry(int count, long totalTime, int version) { - mCount = count; - mTotalTime = totalTime; - mVersion = version; + Entry(int count, long totalTimeUs, long activeTimeUs, int version) { + this.count = count; + this.totalTimeUs = totalTimeUs; + this.activeTimeUs = activeTimeUs; + this.version = version; } } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 837971f17f93..d50a208c1d7e 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -1733,7 +1733,7 @@ public class StatsPullAtomService extends SystemService { String name = ent.getKey(); KernelWakelockStats.Entry kws = ent.getValue(); pulledData.add(FrameworkStatsLog.buildStatsEvent( - atomTag, name, kws.mCount, kws.mVersion, kws.mTotalTime)); + atomTag, name, kws.count, kws.version, kws.totalTimeUs)); } return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 363d2fdf7f4c..044d30b368da 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -53,7 +53,6 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; import android.hardware.biometrics.BiometricAuthenticator.Modality; -import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; @@ -949,14 +948,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, - int userId, long operationId, String opPackageName, long requestId, - @BiometricMultiSensorMode int multiSensorConfig) { + int userId, long operationId, String opPackageName, long requestId) { enforceBiometricDialog(); if (mBar != null) { try { mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed, - requireConfirmation, userId, operationId, opPackageName, requestId, - multiSensorConfig); + requireConfirmation, userId, operationId, opPackageName, requestId); } catch (RemoteException ex) { } } diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java index dc2a97466dea..58c31d565cf3 100644 --- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java +++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java @@ -39,7 +39,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.database.ContentObserver; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -65,8 +64,6 @@ import java.util.function.Supplier; */ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { - private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000; - /** * An absolute threshold at/below which the system clock confidence can be upgraded. i.e. if the * detector receives a high-confidence time and the current system clock is +/- this value from @@ -122,9 +119,8 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { mConfigOriginPrioritiesSupplier = new ConfigOriginPrioritiesSupplier(context); mServerFlagsOriginPrioritiesSupplier = new ServerFlagsOriginPrioritiesSupplier(mServerFlags); - mSystemClockUpdateThresholdMillis = - SystemProperties.getInt("ro.sys.time_detector_update_diff", - SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT); + mSystemClockUpdateThresholdMillis = context.getResources().getInteger( + R.integer.config_timeDetectorAutoUpdateDiffMillis); // Wire up the config change listeners for anything that could affect ConfigurationInternal. // Use the main thread for event delivery, listeners can post to their chosen thread. diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 9add53751e2f..a079875a23e4 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -82,6 +82,7 @@ import android.os.IBinder; import android.os.IInterface; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; @@ -2514,6 +2515,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * Propagate a wake event to the wallpaper engine(s). */ public void notifyWakingUp(int x, int y, @NonNull Bundle extras) { + checkCallerIsSystemOrSystemUi(); synchronized (mLock) { if (mIsLockscreenLiveWallpaperEnabled) { for (WallpaperData data : getActiveWallpapers()) { @@ -2551,6 +2553,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * Propagate a sleep event to the wallpaper engine(s). */ public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) { + checkCallerIsSystemOrSystemUi(); synchronized (mLock) { if (mIsLockscreenLiveWallpaperEnabled) { for (WallpaperData data : getActiveWallpapers()) { @@ -3684,6 +3687,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND); } + /** Check that the caller is either system_server or systemui */ + private void checkCallerIsSystemOrSystemUi() { + if (Binder.getCallingUid() != Process.myUid() && mContext.checkCallingPermission( + android.Manifest.permission.STATUS_BAR_SERVICE) != PERMISSION_GRANTED) { + throw new SecurityException("Access denied: only system processes can call this"); + } + } + /** * Certain user types do not support wallpapers (e.g. managed profiles). The check is * implemented through through the OP_WRITE_WALLPAPER AppOp. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d84c01309286..3db031510317 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3570,6 +3570,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Tell window manager to prepare for this one to be removed. setVisibility(false); + // Propagate the last IME visibility in the same task, so the IME can show + // automatically if the next activity has a focused editable view. + if (mLastImeShown && mTransitionController.isShellTransitionsEnabled()) { + final ActivityRecord nextRunning = task.topRunningActivity(); + if (nextRunning != null) { + nextRunning.mLastImeShown = true; + } + } if (getTaskFragment().getPausingActivity() == null) { ProtoLog.v(WM_DEBUG_STATES, "Finish needs to pause: %s", this); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 1360a956dc0b..750ed986f567 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5342,6 +5342,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return null; } + /** + * Returns the {@link WindowProcessController} for the app process for the given uid and pid. + * + * If no such {@link WindowProcessController} is found, it does not belong to an app, or the + * pid does not match the uid {@code null} is returned. + */ WindowProcessController getProcessController(int pid, int uid) { final WindowProcessController proc = mProcessMap.getProcess(pid); if (proc == null) return null; @@ -5351,6 +5357,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return null; } + /** + * Returns the package name if (and only if) the package name can be uniquely determined. + * Otherwise returns {@code null}. + * + * The provided pid must match the provided uid, otherwise this also returns null. + */ + @Nullable String getPackageNameIfUnique(int uid, int pid) { + final WindowProcessController proc = mProcessMap.getProcess(pid); + if (proc == null || proc.mUid != uid) { + Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") has no WPC"); + return null; + } + List<String> realCallingPackages = proc.getPackageList(); + if (realCallingPackages.size() != 1) { + Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") is ambiguous: " + + realCallingPackages); + return null; + } + return realCallingPackages.get(0); + } + /** A uid is considered to be foreground if it has a visible non-toast window. */ @HotPath(caller = HotPath.START_SERVICE) boolean hasActiveVisibleWindow(int uid) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index dc49e8cea18b..b216578262b4 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -180,7 +180,8 @@ public class BackgroundActivityStartController { Intent intent, ActivityOptions checkedOptions) { return checkBackgroundActivityStart(callingUid, callingPid, callingPackage, - realCallingUid, realCallingPid, callerApp, originatingPendingIntent, + realCallingUid, realCallingPid, + callerApp, originatingPendingIntent, backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK; } @@ -288,11 +289,13 @@ public class BackgroundActivityStartController { } } + String realCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid); + // Legacy behavior allows to use caller foreground state to bypass BAL restriction. // The options here are the options passed by the sender and not those on the intent. final BackgroundStartPrivileges balAllowedByPiSender = PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller( - checkedOptions, realCallingUid); + checkedOptions, realCallingUid, realCallingPackage); final boolean logVerdictChangeByPiDefaultChange = checkedOptions == null || checkedOptions.getPendingIntentBackgroundActivityStartMode() @@ -460,8 +463,11 @@ public class BackgroundActivityStartController { // If we are here, it means all exemptions not based on PI sender failed, so we'll block // unless resultIfPiSenderAllowsBal is an allow and the PI sender allows BAL - String realCallingPackage = callingUid == realCallingUid ? callingPackage : - mService.mContext.getPackageManager().getNameForUid(realCallingUid); + if (realCallingPackage == null) { + realCallingPackage = (callingUid == realCallingUid ? callingPackage : + mService.mContext.getPackageManager().getNameForUid(realCallingUid)) + + "[debugOnly]"; + } String stateDumpLog = " [callingPackage: " + callingPackage + "; callingUid: " + callingUid diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e51f3120ed44..b7327528b069 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -271,13 +271,13 @@ public class DisplayPolicy { private WindowState mSystemUiControllingWindow; // Candidate window to determine the color of navigation bar. The window needs to be top - // fullscreen-app windows or dim layers that are intersecting with the window frame of status - // bar. + // fullscreen-app windows or dim layers that are intersecting with the window frame of + // navigation bar. private WindowState mNavBarColorWindowCandidate; - // The window to determine opacity and background of translucent navigation bar. The window - // needs to be opaque. - private WindowState mNavBarBackgroundWindow; + // Candidate window to determine opacity and background of translucent navigation bar. + // The window frame must intersect the frame of navigation bar. + private WindowState mNavBarBackgroundWindowCandidate; /** * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies @@ -961,12 +961,6 @@ public class DisplayPolicy { if (!win.mSession.mCanSetUnrestrictedGestureExclusion) { attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; } - - final InsetsSourceProvider provider = win.getControllableInsetProvider(); - if (provider != null && provider.getSource().insetsRoundedCornerFrame() - != attrs.insetsRoundedCornerFrame) { - provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame); - } } /** @@ -1104,9 +1098,11 @@ public class DisplayPolicy { } else { overrideProviders = null; } - mDisplayContent.getInsetsStateController().getOrCreateSourceProvider( - provider.getId(), provider.getType()).setWindowContainer( - win, frameProvider, overrideProviders); + final InsetsSourceProvider sourceProvider = mDisplayContent + .getInsetsStateController().getOrCreateSourceProvider(provider.getId(), + provider.getType()); + sourceProvider.getSource().setFlags(provider.getFlags()); + sourceProvider.setWindowContainer(win, frameProvider, overrideProviders); mInsetsSourceWindowsExceptIme.add(win); } } @@ -1387,7 +1383,7 @@ public class DisplayPolicy { mBottomGestureHost = null; mTopFullscreenOpaqueWindowState = null; mNavBarColorWindowCandidate = null; - mNavBarBackgroundWindow = null; + mNavBarBackgroundWindowCandidate = null; mStatusBarAppearanceRegionList.clear(); mLetterboxDetails.clear(); mStatusBarBackgroundWindows.clear(); @@ -1514,8 +1510,8 @@ public class DisplayPolicy { mNavBarColorWindowCandidate = win; addSystemBarColorApp(win); } - if (mNavBarBackgroundWindow == null) { - mNavBarBackgroundWindow = win; + if (mNavBarBackgroundWindowCandidate == null) { + mNavBarBackgroundWindowCandidate = win; } } @@ -1539,12 +1535,19 @@ public class DisplayPolicy { } if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) { mNavBarColorWindowCandidate = win; + addSystemBarColorApp(win); } - } else if (appWindow && attached == null && mNavBarColorWindowCandidate == null + } else if (appWindow && attached == null + && (mNavBarColorWindowCandidate == null || mNavBarBackgroundWindowCandidate == null) && win.getFrame().contains( getBarContentFrameForWindow(win, Type.navigationBars()))) { - mNavBarColorWindowCandidate = win; - addSystemBarColorApp(win); + if (mNavBarColorWindowCandidate == null) { + mNavBarColorWindowCandidate = win; + addSystemBarColorApp(win); + } + if (mNavBarBackgroundWindowCandidate == null) { + mNavBarBackgroundWindowCandidate = win; + } } } @@ -2465,7 +2468,7 @@ public class DisplayPolicy { return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, type)); } - private boolean drawsBarBackground(WindowState win) { + private static boolean drawsBarBackground(WindowState win) { if (win == null) { return true; } @@ -2505,7 +2508,11 @@ public class DisplayPolicy { */ private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible, boolean freeformRootTaskVisible) { - final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow); + final WindowState navBackgroundWin = chooseNavigationBackgroundWindow( + mNavBarBackgroundWindowCandidate, + mDisplayContent.mInputMethodWindow, + mNavigationBarPosition); + final boolean drawBackground = navBackgroundWin != null; if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) { if (drawBackground) { @@ -2525,7 +2532,7 @@ public class DisplayPolicy { } } - if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, Type.navigationBars())) { + if (!isFullyTransparentAllowed(navBackgroundWin, Type.navigationBars())) { appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS; } @@ -2536,6 +2543,20 @@ public class DisplayPolicy { return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS; } + @VisibleForTesting + @Nullable + static WindowState chooseNavigationBackgroundWindow(WindowState candidate, + WindowState imeWindow, @NavigationBarPosition int navBarPosition) { + if (imeWindow != null && imeWindow.isVisible() && navBarPosition == NAV_BAR_BOTTOM + && drawsBarBackground(imeWindow)) { + return imeWindow; + } + if (drawsBarBackground(candidate)) { + return candidate; + } + return null; + } + private boolean isImmersiveMode(WindowState win) { if (win == null) { return false; @@ -2708,9 +2729,9 @@ public class DisplayPolicy { pw.print(prefix); pw.print("mNavBarColorWindowCandidate="); pw.println(mNavBarColorWindowCandidate); } - if (mNavBarBackgroundWindow != null) { - pw.print(prefix); pw.print("mNavBarBackgroundWindow="); - pw.println(mNavBarBackgroundWindow); + if (mNavBarBackgroundWindowCandidate != null) { + pw.print(prefix); pw.print("mNavBarBackgroundWindowCandidate="); + pw.println(mNavBarBackgroundWindowCandidate); } if (mLastStatusBarAppearanceRegions != null) { pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions="); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index b7eaf259ea7a..7f845e6c1ead 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -175,7 +175,7 @@ class InsetsSourceProvider { if (windowContainer == null) { setServerVisible(false); mSource.setVisibleFrame(null); - mSource.setInsetsRoundedCornerFrame(false); + mSource.setFlags(0, 0xffffffff); mSourceFrame.setEmpty(); } else { mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index d83c8612b9e9..c2439888db43 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1428,7 +1428,8 @@ final class LetterboxUiController { for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame() && source.isVisible()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER) + && source.isVisible()) { return source; } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index bb6f8056acda..9ce788686e85 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -49,6 +49,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.SurfaceControl.METADATA_TASK_ID; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; @@ -2858,7 +2859,7 @@ class Task extends TaskFragment { getDisplayContent().getInsetsStateController().getRawInsetsState(); for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); - if (source.insetsRoundedCornerFrame()) { + if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) { animationBounds.inset(source.calculateVisibleInsets(animationBounds)); } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c763cfac4e88..0ce794fdb2ba 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -382,11 +382,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks, // so ChangeInfo#hasChanged() can return true to report the transition info. for (int i = mChanges.size() - 1; i >= 0; --i) { - final WindowContainer<?> wc = mChanges.keyAt(i); - if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue; - if (isInTransientHide(wc)) { - mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH; - } + updateTransientFlags(mChanges.valueAt(i)); } } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " @@ -581,7 +577,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { for (WindowContainer<?> curr = getAnimatableParent(wc); curr != null && !mChanges.containsKey(curr); curr = getAnimatableParent(curr)) { - mChanges.put(curr, new ChangeInfo(curr)); + final ChangeInfo info = new ChangeInfo(curr); + updateTransientFlags(info); + mChanges.put(curr, info); if (isReadyGroup(curr)) { mReadyTracker.addGroup(curr); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" @@ -600,6 +598,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { ChangeInfo info = mChanges.get(wc); if (info == null) { info = new ChangeInfo(wc); + updateTransientFlags(info); mChanges.put(wc, info); } mParticipants.add(wc); @@ -615,6 +614,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } + private void updateTransientFlags(@NonNull ChangeInfo info) { + final WindowContainer<?> wc = info.mContainer; + // Only look at tasks, taskfragments, or activities + if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return; + if (!isInTransientHide(wc)) return; + info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH; + } + private void recordDisplay(DisplayContent dc) { if (dc == null || mTargetDisplays.contains(dc)) return; mTargetDisplays.add(dc); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 0152666a830d..4bc4c266c114 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -4141,7 +4141,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< getDisplayContent().getInsetsStateController().getSourceProviders(); for (int i = providers.size(); i >= 0; i--) { final InsetsSourceProvider insetProvider = providers.valueAt(i); - if (!insetProvider.getSource().insetsRoundedCornerFrame()) { + if (!insetProvider.getSource().hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { return; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 28220f4bf3be..a71329637733 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9231,7 +9231,6 @@ public class WindowManagerService extends IWindowManager.Stub boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { final Task imeTargetWindowTask; - boolean hadRequestedShowIme = false; synchronized (mGlobalLock) { final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken); if (imeTargetWindow == null) { @@ -9241,14 +9240,15 @@ public class WindowManagerService extends IWindowManager.Stub if (imeTargetWindowTask == null) { return false; } - if (imeTargetWindow.mActivityRecord != null) { - hadRequestedShowIme = imeTargetWindow.mActivityRecord.mLastImeShown; + if (imeTargetWindow.mActivityRecord != null + && imeTargetWindow.mActivityRecord.mLastImeShown) { + return true; } } final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId, imeTargetWindowTask.mUserId, false /* isLowResolution */, false /* restoreFromDisk */); - return snapshot != null && snapshot.hasImeSurface() || hadRequestedShowIme; + return snapshot != null && snapshot.hasImeSurface(); } @Override diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index dbd9e4b8ea68..3672820c13ad 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -721,6 +721,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } + List<String> getPackageList() { + synchronized (mPkgList) { + return new ArrayList<>(mPkgList); + } + } + void addActivityIfNeeded(ActivityRecord r) { // even if we already track this activity, note down that it has been launched setLastActivityLaunchTime(r); diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml index 69e13b38873a..070bd4b1c5a9 100644 --- a/services/core/lint-baseline.xml +++ b/services/core/lint-baseline.xml @@ -137,4 +137,12 @@ line="1448"/> </issue> + <issue + id="SimpleManualPermissionEnforcement" + message="IWindowManager permission check should be converted to @EnforcePermission annotation"> + <location + file="out/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java" + line="7158"/> + </issue> + </issues> diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml new file mode 100644 index 000000000000..8489c17dd878 --- /dev/null +++ b/services/lint-baseline.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev"> + + <issue + id="SimpleManualPermissionEnforcement" + message="ISystemConfig permission check should be converted to @EnforcePermission annotation" + errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO," + errorLine2=" ^"> + <location + file="frameworks/base/services/java/com/android/server/SystemConfigService.java" + line="46" + column="13"/> + </issue> + + <issue + id="SimpleManualPermissionEnforcement" + message="ISystemConfig permission check should be converted to @EnforcePermission annotation" + errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO," + errorLine2=" ^"> + <location + file="frameworks/base/services/java/com/android/server/SystemConfigService.java" + line="54" + column="13"/> + </issue> + + <issue + id="SimpleManualPermissionEnforcement" + message="ISystemConfig permission check should be converted to @EnforcePermission annotation" + errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO," + errorLine2=" ^"> + <location + file="frameworks/base/services/java/com/android/server/SystemConfigService.java" + line="67" + column="13"/> + </issue> + + <issue + id="SimpleManualPermissionEnforcement" + message="ISystemConfig permission check should be converted to @EnforcePermission annotation" + errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS," + errorLine2=" ^"> + <location + file="frameworks/base/services/java/com/android/server/SystemConfigService.java" + line="76" + column="13"/> + </issue> + + <issue + id="SimpleManualPermissionEnforcement" + message="ISystemConfig permission check should be converted to @EnforcePermission annotation" + errorLine1=" getContext().enforceCallingOrSelfPermission(Manifest.permission.QUERY_ALL_PACKAGES," + errorLine2=" ^"> + <location + file="frameworks/base/services/java/com/android/server/SystemConfigService.java" + line="107" + column="13"/> + </issue> + +</issues> diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt index 106c3a83e373..a3f65af6bc82 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt @@ -22,7 +22,7 @@ import android.os.Message import android.os.SystemClock import android.os.UserHandle import android.util.AtomicFile -import android.util.Log +import android.util.Slog import android.util.SparseLongArray import com.android.internal.annotations.GuardedBy import com.android.internal.os.BackgroundThread @@ -98,7 +98,7 @@ class AccessPersistence( AtomicFile(this).readWithReserveCopy { it.parseBinaryXml(block) } true } catch (e: FileNotFoundException) { - Log.i(LOG_TAG, "$this not found") + Slog.i(LOG_TAG, "$this not found") false } catch (e: Exception) { throw IllegalStateException("Failed to read $this", e) @@ -176,7 +176,7 @@ class AccessPersistence( try { AtomicFile(this).writeWithReserveCopy { it.serializeBinaryXml(block) } } catch (e: Exception) { - Log.e(LOG_TAG, "Failed to serialize $this", e) + Slog.e(LOG_TAG, "Failed to serialize $this", e) } } diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt index 4096132d5741..f14434b6a79c 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -16,7 +16,7 @@ package com.android.server.permission.access -import android.util.Log +import android.util.Slog import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer import com.android.server.SystemConfig @@ -326,7 +326,7 @@ class AccessPolicy private constructor( VERSION_LATEST } version == VERSION_LATEST -> {} - else -> Log.w( + else -> Slog.w( LOG_TAG, "Unexpected version $version for package $packageName," + "latest version is $VERSION_LATEST" ) @@ -343,7 +343,7 @@ class AccessPolicy private constructor( } } } - else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state") } } } @@ -372,7 +372,7 @@ class AccessPolicy private constructor( } } else -> { - Log.w( + Slog.w( LOG_TAG, "Ignoring unknown tag $tagName when parsing user state for user $userId" ) @@ -387,12 +387,12 @@ class AccessPolicy private constructor( forEachTag { when (tagName) { TAG_PACKAGE -> parsePackageVersion(packageVersions) - else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing package versions") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing package versions") } } packageVersions.forEachReversedIndexed { packageVersionIndex, packageName, _ -> if (packageName !in state.externalState.packageStates) { - Log.w(LOG_TAG, "Dropping unknown $packageName when parsing package versions") + Slog.w(LOG_TAG, "Dropping unknown $packageName when parsing package versions") packageVersions.removeAt(packageVersionIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt index d83beab0975e..96d315e923ba 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt @@ -17,7 +17,7 @@ package com.android.server.permission.access.appop import android.os.Process -import android.util.Log +import android.util.Slog import com.android.server.LocalServices import com.android.server.appop.AppOpMigrationHelper import com.android.server.permission.access.MutableAccessState @@ -40,7 +40,7 @@ class AppIdAppOpMigration { val packageNames = state.externalState.appIdPackageNames[appId] // Non-application UIDs may not have an Android package but may still have app op state. if (packageNames == null && appId >= Process.FIRST_APPLICATION_UID) { - Log.w(LOG_TAG, "Dropping unknown app ID $appId when migrating app op state") + Slog.w(LOG_TAG, "Dropping unknown app ID $appId when migrating app op state") return@forEach } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt index 175ec4bfadd1..4c7e94688d00 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt @@ -17,7 +17,7 @@ package com.android.server.permission.access.appop import android.os.Process -import android.util.Log +import android.util.Slog import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer import com.android.server.permission.access.AccessState @@ -46,14 +46,14 @@ class AppIdAppOpPersistence : BaseAppOpPersistence() { forEachTag { when (tagName) { TAG_APP_ID -> parseAppId(appIdAppOpModes) - else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") } } userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ -> // Non-application UIDs may not have an Android package but may still have app op state. if (appId !in state.externalState.appIdPackageNames && appId >= Process.FIRST_APPLICATION_UID) { - Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state") + Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state") appIdAppOpModes.removeAt(appIdIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) } diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt index 53e53927cef1..a267637dd0c4 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt @@ -16,7 +16,7 @@ package com.android.server.permission.access.appop -import android.util.Log +import android.util.Slog import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer import com.android.server.permission.access.AccessState @@ -40,7 +40,7 @@ abstract class BaseAppOpPersistence { forEachTag { when (tagName) { TAG_APP_OP -> parseAppOp(appOpModes) - else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") } } } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt index 3044db692eb9..03311a238410 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt @@ -16,7 +16,7 @@ package com.android.server.permission.access.appop -import android.util.Log +import android.util.Slog import com.android.server.LocalServices import com.android.server.appop.AppOpMigrationHelper import com.android.server.permission.access.MutableAccessState @@ -38,7 +38,7 @@ class PackageAppOpMigration { val packageAppOpModes = userState.mutatePackageAppOpModes() legacyPackageAppOpModes.forEach { (packageName, legacyAppOpModes) -> if (packageName !in state.externalState.packageStates) { - Log.w(LOG_TAG, "Dropping unknown package $packageName when migrating app op state") + Slog.w(LOG_TAG, "Dropping unknown package $packageName when migrating app op state") return@forEach } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt index 347002a80f08..169bd6970a78 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt @@ -16,7 +16,7 @@ package com.android.server.permission.access.appop -import android.util.Log +import android.util.Slog import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer import com.android.server.permission.access.AccessState @@ -46,12 +46,12 @@ class PackageAppOpPersistence : BaseAppOpPersistence() { forEachTag { when (tagName) { TAG_PACKAGE -> parsePackage(packageAppOpModes) - else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") } } packageAppOpModes.forEachReversedIndexed { packageNameIndex, packageName, _ -> if (packageName !in state.externalState.packageStates) { - Log.w(LOG_TAG, "Dropping unknown package $packageName when parsing app-op state") + Slog.w(LOG_TAG, "Dropping unknown package $packageName when parsing app-op state") packageAppOpModes.removeAt(packageNameIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) } diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt index d53ec80bf29d..691ed8f10220 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt @@ -16,7 +16,7 @@ package com.android.server.permission.access.permission -import android.util.Log +import android.util.Slog import com.android.server.LocalServices import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports @@ -51,7 +51,7 @@ class AppIdPermissionMigration { ) permissions[permission.name] = permission if (DEBUG_MIGRATION) { - Log.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " + + Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " + "${permission.type}, appId: ${permission.appId}, protectionLevel: " + "${permission.protectionLevel}, tree: $isPermissionTree" ) @@ -75,7 +75,7 @@ class AppIdPermissionMigration { legacyAppIdPermissionStates.forEach { (appId, legacyPermissionStates) -> val packageNames = state.externalState.appIdPackageNames[appId] if (packageNames == null) { - Log.w(LOG_TAG, "Dropping unknown app ID $appId when migrating permission state") + Slog.w(LOG_TAG, "Dropping unknown app ID $appId when migrating permission state") return@forEach } @@ -85,7 +85,7 @@ class AppIdPermissionMigration { (permissionName, legacyPermissionState) -> val permission = state.systemState.permissions[permissionName] if (permission == null) { - Log.w( + Slog.w( LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" + " when migrating permission state" ) @@ -138,7 +138,7 @@ class AppIdPermissionMigration { val oldGrantState = legacyPermissionState.isGranted val newGrantState = PermissionFlags.isPermissionGranted(flags) val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags) - Log.v( + Slog.v( LOG_TAG, "Migrated appId: $appId, permission: " + "${permission.name}, user: $userId, oldGrantState: $oldGrantState" + ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " + diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt index 615bbdc71f85..fffc7031326a 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt @@ -17,7 +17,7 @@ package com.android.server.permission.access.permission import android.content.pm.PermissionInfo -import android.util.Log +import android.util.Slog import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer import com.android.server.permission.access.AccessState @@ -27,6 +27,7 @@ import com.android.server.permission.access.MutableAppIdPermissionFlags import com.android.server.permission.access.WriteMode import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.util.andInv import com.android.server.permission.access.util.attribute import com.android.server.permission.access.util.attributeInt import com.android.server.permission.access.util.attributeIntHex @@ -38,6 +39,7 @@ import com.android.server.permission.access.util.getAttributeIntHexOrThrow import com.android.server.permission.access.util.getAttributeIntOrThrow import com.android.server.permission.access.util.getAttributeValue import com.android.server.permission.access.util.getAttributeValueOrThrow +import com.android.server.permission.access.util.hasBits import com.android.server.permission.access.util.tag import com.android.server.permission.access.util.tagName @@ -63,7 +65,7 @@ class AppIdPermissionPersistence { forEachTag { when (val tagName = tagName) { TAG_PERMISSION -> parsePermission(permissions) - else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions") } } permissions.forEachReversedIndexed { permissionIndex, _, permission -> @@ -71,7 +73,7 @@ class AppIdPermissionPersistence { val externalState = state.externalState if (packageName !in externalState.packageStates && packageName !in externalState.disabledSystemPackageStates) { - Log.w( + Slog.w( LOG_TAG, "Dropping permission with unknown package $packageName when parsing permissions" ) @@ -95,7 +97,7 @@ class AppIdPermissionPersistence { when (type) { Permission.TYPE_MANIFEST -> {} Permission.TYPE_CONFIG -> { - Log.w(LOG_TAG, "Ignoring unexpected config permission $name") + Slog.w(LOG_TAG, "Ignoring unexpected config permission $name") return } Permission.TYPE_DYNAMIC -> { @@ -105,7 +107,7 @@ class AppIdPermissionPersistence { } } else -> { - Log.w(LOG_TAG, "Ignoring permission $name with unknown type $type") + Slog.w(LOG_TAG, "Ignoring permission $name with unknown type $type") return } } @@ -134,7 +136,7 @@ class AppIdPermissionPersistence { Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {} Permission.TYPE_CONFIG -> return else -> { - Log.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type") + Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type") return } } @@ -164,12 +166,12 @@ class AppIdPermissionPersistence { forEachTag { when (tagName) { TAG_APP_ID -> parseAppId(appIdPermissionFlags) - else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") } } appIdPermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ -> if (appId !in state.externalState.appIdPackageNames) { - Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state") + Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state") appIdPermissionFlags.removeAt(appIdIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) } @@ -183,7 +185,7 @@ class AppIdPermissionPersistence { forEachTag { when (tagName) { TAG_PERMISSION -> parseAppIdPermission(permissionFlags) - else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") } } } @@ -225,7 +227,13 @@ class AppIdPermissionPersistence { private fun BinaryXmlSerializer.serializeAppIdPermission(name: String, flags: Int) { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) - attributeInt(ATTR_FLAGS, flags) + // Never serialize one-time permissions as granted. + val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } + attributeInt(ATTR_FLAGS, serializedFlags) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index e395d75f0c9e..0e62d25554c8 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -22,7 +22,7 @@ import android.content.pm.PermissionGroupInfo import android.content.pm.PermissionInfo import android.content.pm.SigningDetails import android.os.Build -import android.util.Log +import android.util.Slog import com.android.internal.os.RoSystemProperties import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer @@ -339,14 +339,14 @@ class AppIdPermissionPolicy : SchemePolicy() { val originalPackageState = newState.externalState.packageStates[originalPackageName] ?: return false if (!originalPackageState.isSystem) { - Log.w( + Slog.w( LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package not in system partition" ) return false } if (originalPackageState.androidPackage != null) { - Log.w( + Slog.w( LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package still exists" ) @@ -361,7 +361,7 @@ class AppIdPermissionPolicy : SchemePolicy() { // app is non-instant in at least one user. val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp } if (isInstantApp) { - Log.w( + Slog.w( LOG_TAG, "Ignoring permission groups declared in package" + " ${packageState.packageName}: instant apps cannot declare permission groups" ) @@ -383,7 +383,7 @@ class AppIdPermissionPolicy : SchemePolicy() { // non-system apps, we now allow system apps to override permission groups similar // to permissions so that we no longer need to rely on the scan order. if (!packageState.isSystem) { - Log.w( + Slog.w( LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + " package $newPackageName: already declared in another" + " package $oldPackageName" @@ -391,14 +391,14 @@ class AppIdPermissionPolicy : SchemePolicy() { return@forEachIndexed } if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) { - Log.w( + Slog.w( LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + " system package $newPackageName: already declared in another" + " system package $oldPackageName" ) return@forEachIndexed } - Log.w( + Slog.w( LOG_TAG, "Overriding permission group $permissionGroupName with" + " new declaration in system package $newPackageName: originally" + " declared in another package $oldPackageName" @@ -429,7 +429,7 @@ class AppIdPermissionPolicy : SchemePolicy() { val permissionTree = findPermissionTree(permissionName) val newPackageName = newPermissionInfo.packageName if (permissionTree != null && newPackageName != permissionTree.packageName) { - Log.w( + Slog.w( LOG_TAG, "Ignoring permission $permissionName declared in package" + " $newPackageName: base permission tree ${permissionTree.name} is" + " declared in another package ${permissionTree.packageName}" @@ -441,7 +441,7 @@ class AppIdPermissionPolicy : SchemePolicy() { val oldPackageName = oldPermission.packageName // Only allow system apps to redefine non-system permissions. if (!packageState.isSystem) { - Log.w( + Slog.w( LOG_TAG, "Ignoring permission $permissionName declared in package" + " $newPackageName: already declared in another package" + " $oldPackageName" @@ -455,7 +455,7 @@ class AppIdPermissionPolicy : SchemePolicy() { appId = packageState.appId ) } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) { - Log.w( + Slog.w( LOG_TAG, "Overriding permission $permissionName with new declaration in" + " system package $newPackageName: originally declared in another" + " package $oldPackageName" @@ -474,7 +474,7 @@ class AppIdPermissionPolicy : SchemePolicy() { oldPermission.gids, oldPermission.areGidsPerUser ) } else { - Log.w( + Slog.w( LOG_TAG, "Ignoring permission $permissionName declared in system package" + " $newPackageName: already declared in another system package" + " $oldPackageName" @@ -498,7 +498,7 @@ class AppIdPermissionPolicy : SchemePolicy() { // the group is already granted. Hence if the group of // a granted permission changes we need to revoke it to // avoid having permissions of the new group auto-granted. - Log.w( + Slog.w( LOG_TAG, "Revoking runtime permission $permissionName for" + " appId $appId and userId $userId as the permission" + " group changed from ${oldPermission.groupName}" + @@ -506,7 +506,7 @@ class AppIdPermissionPolicy : SchemePolicy() { ) } if (isPermissionTypeChanged) { - Log.w( + Slog.w( LOG_TAG, "Revoking permission $permissionName for" + " appId $appId and userId $userId as the permission" + " type changed." @@ -660,7 +660,7 @@ class AppIdPermissionPolicy : SchemePolicy() { !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage if ((isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded) && oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) { - Log.v(LOG_TAG, "Revoking storage permission: $permissionName for appId: " + + Slog.v(LOG_TAG, "Revoking storage permission: $permissionName for appId: " + " $appId and user: $userId") val newFlags = oldFlags andInv ( PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK @@ -749,12 +749,17 @@ class AppIdPermissionPolicy : SchemePolicy() { // If this is an existing, non-system package, // then we can't add any new permissions to it. // Except if this is a permission that was added to the platform - val newFlags = if (!wasRevoked || isRequestedByInstalledPackage || + var newFlags = if (!wasRevoked || isRequestedByInstalledPackage || isRequestedBySystemPackage || isCompatibilityPermission) { PermissionFlags.INSTALL_GRANTED } else { PermissionFlags.INSTALL_REVOKED } + if (permission.isAppOp) { + newFlags = newFlags or ( + oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) + ) + } setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { @@ -784,6 +789,11 @@ class AppIdPermissionPolicy : SchemePolicy() { 0 } } + if (permission.isAppOp) { + newFlags = newFlags or ( + oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) + ) + } // Different from the old implementation, which seemingly allows granting an // unallowlisted privileged permission via development or role but revokes it upon next // reconciliation, we now properly allows that because the privileged protection flag @@ -917,7 +927,7 @@ class AppIdPermissionPolicy : SchemePolicy() { } setPermissionFlags(appId, userId, permissionName, newFlags) } else { - Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" + + Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" + "for permission ${permission.name} while evaluating permission state" + "for appId $appId and userId $userId") } @@ -977,7 +987,7 @@ class AppIdPermissionPolicy : SchemePolicy() { for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) { if (compatibilityPermission.name == permissionName && androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) { - Log.i( + Slog.i( LOG_TAG, "Auto-granting $permissionName to old package" + " ${androidPackage.packageName}" ) @@ -1041,7 +1051,7 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!newState.externalState.isSystemReady) { // Apps that are in updated apex's do not need to be allowlisted if (!packageState.isApkInUpdatedApex) { - Log.w( + Slog.w( LOG_TAG, "Privileged permission ${permission.name} for package" + " ${packageState.packageName} (${packageState.path}) not in" + " privileged permission allowlist" @@ -1084,7 +1094,7 @@ class AppIdPermissionPolicy : SchemePolicy() { if (nonApexAllowlistState != null) { // TODO(andreionea): Remove check as soon as all apk-in-apex // permission allowlists are migrated. - Log.w( + Slog.w( LOG_TAG, "Package $packageName is an APK in APEX but has permission" + " allowlist on the system image, please bundle the allowlist in the" + " $apexModuleName APEX instead" @@ -1274,7 +1284,7 @@ class AppIdPermissionPolicy : SchemePolicy() { // if the permission's protectionLevel does not have the extra vendorPrivileged // flag. if (packageState.isVendor && !permission.isVendorPrivileged) { - Log.w( + Slog.w( LOG_TAG, "Permission $permissionName cannot be granted to privileged" + " vendor app $packageName because it isn't a vendorPrivileged" + " permission" diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt index 8ed874798d84..875183cc941b 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt @@ -18,7 +18,7 @@ package com.android.server.permission.access.permission import android.Manifest import android.os.Build -import android.util.Log +import android.util.Slog import com.android.server.permission.access.MutateStateScope import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports @@ -42,7 +42,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { ) { val packageName = packageState.packageName if (version <= 3) { - Log.v( + Slog.v( LOG_TAG, "Allowlisting and upgrading background location permission for " + "package: $packageName, version: $version, user:$userId" ) @@ -50,7 +50,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { upgradeBackgroundLocationPermission(packageState, userId) } if (version <= 10) { - Log.v( + Slog.v( LOG_TAG, "Upgrading access media location permission for package: $packageName" + ", version: $version, user: $userId" ) @@ -58,7 +58,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } // Enable isAtLeastT check, when moving subsystem to mainline. if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) { - Log.v( + Slog.v( LOG_TAG, "Upgrading scoped permissions for package: $packageName" + ", version: $version, user: $userId" ) @@ -159,7 +159,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { userId: Int, permissionName: String ) { - Log.v( + Slog.v( LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " + "permission: $permissionName, userId: $userId" ) @@ -171,7 +171,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { val appId = packageState.appId var flags = with(policy) { getPermissionFlags(appId, userId, permissionName) } if (flags.hasAnyBit(MASK_ANY_FIXED)) { - Log.v( + Slog.v( LOG_TAG, "Not allowed to grant $permissionName to package ${packageState.packageName}" ) diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index ac437768c5ac..605f6ba00c4f 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -50,7 +50,7 @@ import android.util.ArraySet import android.util.DebugUtils import android.util.IndentingPrintWriter import android.util.IntArray as GrowingIntArray -import android.util.Log +import android.util.Slog import android.util.SparseBooleanArray import com.android.internal.compat.IPlatformCompat import com.android.internal.logging.MetricsLogger @@ -470,7 +470,7 @@ class PermissionService( val packageState = packageManagerInternal.getPackageStateInternal(androidPackage.packageName) if (packageState == null) { - Log.e( + Slog.e( LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" + " $androidPackage" ) @@ -587,7 +587,7 @@ class PermissionService( val packageState = packageManagerLocal.withUnfilteredSnapshot() .use { it.getPackageState(packageName) } if (packageState == null) { - Log.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName") + Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName") return emptySet() } @@ -682,7 +682,7 @@ class PermissionService( if (isDebugEnabled && PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { val callingUidName = packageManagerInternal.getNameForUid(callingUid) - Log.i( + Slog.i( LOG_TAG, "$methodName(packageName = $packageName," + " permissionName = $permissionName" + (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") + @@ -692,7 +692,7 @@ class PermissionService( } if (!userManagerInternal.exists(userId)) { - Log.w(LOG_TAG, "$methodName: Unknown user $userId") + Slog.w(LOG_TAG, "$methodName: Unknown user $userId") return } @@ -722,7 +722,7 @@ class PermissionService( // throws when package exists but isn't visible, we now return in both cases to avoid // leaking the package existence. if (androidPackage == null) { - Log.w(LOG_TAG, "$methodName: Unknown package $packageName") + Slog.w(LOG_TAG, "$methodName: Unknown package $packageName") return } @@ -760,7 +760,7 @@ class PermissionService( PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED, PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {} else -> { - Log.w( + Slog.w( LOG_TAG, "setRequestedPermissionStates: Unknown permission state" + " $permissionState for permission $permissionName" ) @@ -879,7 +879,7 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) { if (reportError) { - Log.e( + Slog.e( LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" + " for package $packageName" ) @@ -889,7 +889,7 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) { if (reportError) { - Log.e( + Slog.e( LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" + " for package $packageName" ) @@ -899,7 +899,7 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) { if (reportError) { - Log.e( + Slog.e( LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" + " $permissionName to package $packageName" ) @@ -915,7 +915,7 @@ class PermissionService( ) if (!softRestrictedPermissionPolicy.mayGrantPermission()) { if (reportError) { - Log.e( + Slog.e( LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" + " $permissionName to package $packageName" ) @@ -960,7 +960,7 @@ class PermissionService( override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int { if (!userManagerInternal.exists(userId)) { - Log.w(LOG_TAG, "getPermissionFlags: Unknown user $userId") + Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId") return 0 } @@ -977,14 +977,14 @@ class PermissionService( val packageState = packageManagerLocal.withFilteredSnapshot() .use { it.getPackageState(packageName) } if (packageState == null) { - Log.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName") + Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName") return 0 } service.getState { val permission = with(policy) { getPermissions()[permissionName] } if (permission == null) { - Log.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName") + Slog.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName") return 0 } @@ -1000,7 +1000,7 @@ class PermissionService( userId: Int ): Boolean { if (!userManagerInternal.exists(userId)) { - Log.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId") + Slog.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId") return false } @@ -1044,7 +1044,7 @@ class PermissionService( userId: Int ): Boolean { if (!userManagerInternal.exists(userId)) { - Log.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId") + Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId") return false } @@ -1080,7 +1080,7 @@ class PermissionService( BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId ) } catch (e: RemoteException) { - Log.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" + + Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" + " compatibility change is enabled", e) false } @@ -1111,7 +1111,7 @@ class PermissionService( PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) - Log.i( + Slog.i( LOG_TAG, "updatePermissionFlags(packageName = $packageName," + " permissionName = $permissionName, flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + @@ -1120,7 +1120,7 @@ class PermissionService( } if (!userManagerInternal.exists(userId)) { - Log.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId") + Slog.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId") return } @@ -1168,7 +1168,7 @@ class PermissionService( // leaking the package existence. if (androidPackage == null || packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) { - Log.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName") + Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName") return } @@ -1211,7 +1211,7 @@ class PermissionService( PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) - Log.i( + Slog.i( LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + " callingUid = $callingUidName ($callingUid))", RuntimeException() @@ -1219,7 +1219,7 @@ class PermissionService( } if (!userManagerInternal.exists(userId)) { - Log.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId") + Slog.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId") return } @@ -1295,7 +1295,7 @@ class PermissionService( val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) } if (!isPermissionRequested && oldFlags == 0) { - Log.w( + Slog.w( LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" + " $packageName" ) @@ -1316,7 +1316,7 @@ class PermissionService( Preconditions.checkArgumentNonnegative(userId, "userId cannot be null") if (!userManagerInternal.exists(userId)) { - Log.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId") + Slog.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId") return null } @@ -1456,7 +1456,7 @@ class PermissionService( private fun enforceRestrictedPermission(permissionName: String): Boolean { val permission = service.getState { with(policy) { getPermissions()[permissionName] } } if (permission == null) { - Log.w(LOG_TAG, "permission definition for $permissionName does not exist") + Slog.w(LOG_TAG, "permission definition for $permissionName does not exist") return false } @@ -1712,7 +1712,7 @@ class PermissionService( } catch (e: Exception) { when (e) { is TimeoutException, is InterruptedException, is ExecutionException -> { - Log.e(LOG_TAG, "Cannot create permission backup for user $userId", e) + Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e) null } else -> throw e @@ -2389,7 +2389,7 @@ class PermissionService( ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED isInSetup || isInDeferredSetup } catch (e: Settings.SettingNotFoundException) { - Log.w(LOG_TAG, "Failed to check if the user is in restore: $e") + Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e") false } } @@ -2412,7 +2412,7 @@ class PermissionService( try { listener.onPermissionsChanged(uid) } catch (e: RemoteException) { - Log.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e) + Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e) } } } diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt index 2c29332a638b..bd829351941c 100644 --- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -18,7 +18,7 @@ package com.android.server.permission.access.util import android.os.FileUtils import android.util.AtomicFile -import android.util.Log +import android.util.Slog import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -35,12 +35,12 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { } catch (e: FileNotFoundException) { throw e } catch (e: Exception) { - Log.wtf("AccessPersistence", "Failed to read $this", e) + Slog.wtf("AccessPersistence", "Failed to read $this", e) val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") try { AtomicFile(reserveFile).openRead().use(block) } catch (e2: Exception) { - Log.e("AccessPersistence", "Failed to read $reserveFile", e2) + Slog.e("AccessPersistence", "Failed to read $reserveFile", e2) throw e } } @@ -62,7 +62,7 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { } } } catch (e: Exception) { - Log.e("AccessPersistence", "Failed to write $reserveFile", e) + Slog.e("AccessPersistence", "Failed to write $reserveFile", e) } } diff --git a/services/print/lint-baseline.xml b/services/print/lint-baseline.xml new file mode 100644 index 000000000000..1bf031a9e289 --- /dev/null +++ b/services/print/lint-baseline.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev"> + + <issue + id="SimpleManualPermissionEnforcement" + message="IPrintManager permission check should be converted to @EnforcePermission annotation" + errorLine1=" mContext.enforceCallingOrSelfPermission(" + errorLine2=" ^"> + <location + file="frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java" + line="401" + column="13"/> + </issue> + +</issues> diff --git a/services/proguard.flags b/services/proguard.flags index c31abbb5c0d6..e11e613adb5c 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -42,19 +42,6 @@ -keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface -keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface -# Global entities normally kept through explicit Manifest entries -# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/AndroidManifest.xml, -# by including that manifest with the library rule that triggers optimization. --keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.Activity --keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.Service --keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.backup.BackupAgent --keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.content.BroadcastReceiver --keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.content.ContentProvider --keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.preference.Preference --keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.view.View { - public <init>(...); -} - # Various classes subclassed in or referenced via JNI in ethernet-service -keep public class android.net.** { *; } -keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; } 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 ad5f0d7233ca..636576492362 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -269,7 +269,9 @@ public class BroadcastQueueTest { deliverRes = res; break; } + res.setPendingStart(true); mHandlerThread.getThreadHandler().post(() -> { + res.setPendingStart(false); synchronized (mAms) { switch (behavior) { case SUCCESS: @@ -281,6 +283,10 @@ public class BroadcastQueueTest { mActiveProcesses.remove(deliverRes); mQueue.onApplicationTimeoutLocked(deliverRes); break; + case KILLED_WITHOUT_NOTIFY: + mActiveProcesses.remove(res); + res.setKilled(true); + break; default: throw new UnsupportedOperationException(); } @@ -310,6 +316,7 @@ public class BroadcastQueueTest { mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); mConstants.TIMEOUT = 100; mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; + mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500; mSkipPolicy = spy(new BroadcastSkipPolicy(mAms)); doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any()); @@ -381,6 +388,8 @@ public class BroadcastQueueTest { FAIL_TIMEOUT_PREDECESSOR, /** Process fails by immediately returning null */ FAIL_NULL, + /** Process is killed without reporting to BroadcastQueue */ + KILLED_WITHOUT_NOTIFY, } private enum ProcessBehavior { @@ -522,6 +531,11 @@ public class BroadcastQueueTest { return info; } + static BroadcastFilter withPriority(BroadcastFilter filter, int priority) { + filter.setPriority(priority); + return filter; + } + static ResolveInfo makeManifestReceiver(String packageName, String name) { return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM); } @@ -1261,6 +1275,46 @@ public class BroadcastQueueTest { new ComponentName(PACKAGE_GREEN, CLASS_GREEN)); } + /** + * Verify that when BroadcastQueue doesn't get notified when a process gets killed, it + * doesn't get stuck. + */ + @Test + public void testKillWithoutNotify() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + + mNextProcessStartBehavior.set(ProcessStartBehavior.KILLED_WITHOUT_NOTIFY); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of( + withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10), + withPriority(makeRegisteredReceiver(receiverBlueApp), 5), + withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0)))); + + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE)))); + + waitForIdle(); + final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)); + final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW, + getUidForPackage(PACKAGE_YELLOW)); + final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE, + getUidForPackage(PACKAGE_ORANGE)); + + if (mImpl == Impl.MODERN) { + // Modern queue does not retry sending a broadcast once any broadcast delivery fails. + assertNull(receiverGreenApp); + } else { + verifyScheduleReceiver(times(1), receiverGreenApp, airplane); + } + verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane); + verifyScheduleReceiver(times(1), receiverYellowApp, airplane); + verifyScheduleReceiver(times(1), receiverOrangeApp, timezone); + } + @Test public void testCold_Success() throws Exception { doCold(ProcessStartBehavior.SUCCESS); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index 154aa7d4e78e..4268eb924225 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -231,7 +231,14 @@ public class AuthSessionTest { public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes() throws Exception { setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); - testMultiAuth_fingerprintSensorStartsAfterUINotifies(); + testMultiAuth_fingerprintSensorStartsAfterUINotifies(true /* startFingerprintNow */); + } + + @Test + public void testMultiAuth_singleSensor_fingerprintSensorDoesNotStartAfterDialogAnimationCompletes() + throws Exception { + setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); + testMultiAuth_fingerprintSensorStartsAfterUINotifies(false /* startFingerprintNow */); } @Test @@ -239,10 +246,18 @@ public class AuthSessionTest { throws Exception { setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class)); - testMultiAuth_fingerprintSensorStartsAfterUINotifies(); + testMultiAuth_fingerprintSensorStartsAfterUINotifies(true /* startFingerprintNow */); } - public void testMultiAuth_fingerprintSensorStartsAfterUINotifies() + @Test + public void testMultiAuth_fingerprintSensorDoesNotStartAfterDialogAnimationCompletes() + throws Exception { + setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); + setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class)); + testMultiAuth_fingerprintSensorStartsAfterUINotifies(false /* startFingerprintNow */); + } + + public void testMultiAuth_fingerprintSensorStartsAfterUINotifies(boolean startFingerprintNow) throws Exception { final long operationId = 123; final int userId = 10; @@ -282,13 +297,21 @@ public class AuthSessionTest { // fingerprint sensor does not start even if all cookies are received assertEquals(STATE_AUTH_STARTED, session.getState()); verify(mStatusBarService).showAuthenticationDialog(any(), any(), any(), - anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong(), anyInt()); + anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong()); // Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started. - session.onDialogAnimatedIn(); + session.onDialogAnimatedIn(startFingerprintNow); assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState()); - assertEquals(BiometricSensor.STATE_AUTHENTICATING, + assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING + : BiometricSensor.STATE_COOKIE_RETURNED, session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState()); + + // start fingerprint sensor if it was delayed + if (!startFingerprintNow) { + session.onStartFingerprint(); + assertEquals(BiometricSensor.STATE_AUTHENTICATING, + session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState()); + } } @Test @@ -316,14 +339,14 @@ public class AuthSessionTest { verify(impl, never()).startPreparedClient(anyInt()); // First invocation should start the client monitor. - session.onDialogAnimatedIn(); + session.onDialogAnimatedIn(true /* startFingerprintNow */); assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState()); verify(impl).startPreparedClient(anyInt()); // Subsequent invocations should not start the client monitor again. - session.onDialogAnimatedIn(); - session.onDialogAnimatedIn(); - session.onDialogAnimatedIn(); + session.onDialogAnimatedIn(true /* startFingerprintNow */); + session.onDialogAnimatedIn(false /* startFingerprintNow */); + session.onDialogAnimatedIn(true /* startFingerprintNow */); assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState()); verify(impl, times(1)).startPreparedClient(anyInt()); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 520e1c84c74e..67be37616d5f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -18,7 +18,7 @@ package com.android.server.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; -import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI; @@ -311,8 +311,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(TEST_REQUEST_ID), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(TEST_REQUEST_ID)); } @Test @@ -397,8 +396,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(TEST_REQUEST_ID), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(TEST_REQUEST_ID)); } @Test @@ -516,7 +514,7 @@ public class BiometricServiceTest { assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); // startPreparedClient invoked - mBiometricService.mAuthSession.onDialogAnimatedIn(); + mBiometricService.mAuthSession.onDialogAnimatedIn(true /* startFingerprintNow */); verify(mBiometricService.mSensors.get(0).impl) .startPreparedClient(cookieCaptor.getValue()); @@ -530,8 +528,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(TEST_REQUEST_ID), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(TEST_REQUEST_ID)); // Hardware authenticated final byte[] HAT = generateRandomHAT(); @@ -587,8 +584,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(TEST_REQUEST_ID), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(TEST_REQUEST_ID)); } @Test @@ -752,8 +748,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, anyString(), - anyLong() /* requestId */, - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + anyLong() /* requestId */); } @Test @@ -854,8 +849,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(TEST_REQUEST_ID), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(TEST_REQUEST_ID)); } @Test @@ -935,8 +929,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(TEST_REQUEST_ID), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(TEST_REQUEST_ID)); } @Test @@ -1432,8 +1425,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(requestId), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(requestId)); // Requesting strong and credential, when credential is setup resetReceivers(); @@ -1456,8 +1448,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(requestId), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(requestId)); // Un-downgrading the authenticator allows successful strong auth for (BiometricSensor sensor : mBiometricService.mSensors) { @@ -1482,8 +1473,7 @@ public class BiometricServiceTest { anyInt() /* userId */, anyLong() /* operationId */, eq(TEST_PACKAGE_NAME), - eq(requestId), - eq(BIOMETRIC_MULTI_SENSOR_DEFAULT)); + eq(requestId)); } @Test(expected = IllegalStateException.class) diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index 25bd9bcf8d5c..be9f52e00b16 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -17,12 +17,14 @@ package com.android.server.biometrics.sensors.face.aidl; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -41,6 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -82,6 +85,10 @@ public class SensorTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock FaceProvider mFaceProvider; + @Mock + BaseClientMonitor mClientMonitor; + @Mock + AidlSession mCurrentSession; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -161,6 +168,39 @@ public class SensorTest { assertNull(sensor.getSessionForUser(USER_ID)); } + @Test + public void onBinderDied_cancelNonInterruptableClient() { + mLooper.dispatchAll(); + + when(mCurrentSession.getUserId()).thenReturn(USER_ID); + when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID); + when(mClientMonitor.isInterruptable()).thenReturn(false); + + final SensorProps sensorProps = new SensorProps(); + sensorProps.commonProps = new CommonProps(); + sensorProps.commonProps.sensorId = 1; + final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( + sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, + sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.sensorType, sensorProps.supportsDetectInteraction, + sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); + final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, + internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession); + mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler(); + sensor.mCurrentSession = new AidlSession(0, mock(ISession.class), + USER_ID, mHalCallback); + + mScheduler.scheduleClientMonitor(mClientMonitor); + + assertNotNull(mScheduler.getCurrentClient()); + + sensor.onBinderDied(); + + verify(mClientMonitor).cancel(); + assertNull(sensor.getSessionForUser(USER_ID)); + assertNull(mScheduler.getCurrentClient()); + } + private void verifyNotLocked() { assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID)); verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID)); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 0c1346696b58..15d7601dde34 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -17,17 +17,23 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.IBiometricService; +import android.hardware.biometrics.common.CommonProps; +import android.hardware.biometrics.face.SensorProps; import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -37,11 +43,13 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; +import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import org.junit.Before; import org.junit.Test; @@ -76,6 +84,14 @@ public class SensorTest { private BiometricContext mBiometricContext; @Mock private AuthSessionCoordinator mAuthSessionCoordinator; + @Mock + FingerprintProvider mFingerprintProvider; + @Mock + GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; + @Mock + private AidlSession mCurrentSession; + @Mock + private BaseClientMonitor mClientMonitor; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -130,6 +146,40 @@ public class SensorTest { verifyNotLocked(); } + @Test + public void onBinderDied_cancelNonInterruptableClient() { + mLooper.dispatchAll(); + + when(mCurrentSession.getUserId()).thenReturn(USER_ID); + when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID); + when(mClientMonitor.isInterruptable()).thenReturn(false); + + final SensorProps sensorProps = new SensorProps(); + sensorProps.commonProps = new CommonProps(); + sensorProps.commonProps.sensorId = 1; + final FingerprintSensorPropertiesInternal internalProp = new + FingerprintSensorPropertiesInternal( + sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, + sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */); + final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext, + null /* handler */, internalProp, mLockoutResetDispatcher, + mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession); + mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler(); + sensor.mCurrentSession = new AidlSession(0, mock(ISession.class), + USER_ID, mHalCallback); + + mScheduler.scheduleClientMonitor(mClientMonitor); + + assertNotNull(mScheduler.getCurrentClient()); + + sensor.onBinderDied(); + + verify(mClientMonitor).cancel(); + assertNull(sensor.getSessionForUser(USER_ID)); + assertNull(mScheduler.getCurrentClient()); + } + private void verifyNotLocked() { assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID)); verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID)); diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java new file mode 100644 index 000000000000..ba9956a63dca --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java @@ -0,0 +1,236 @@ +/* + * 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.contentprotection; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.pm.PackageInfo; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.collect.ImmutableList; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +/** + * Test for {@link ContentProtectionBlocklistManager}. + * + * <p>Run with: {@code atest + * FrameworksServicesTests: + * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest} + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ContentProtectionBlocklistManagerTest { + + private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name"; + + private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name"; + + private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name"; + + private static final String PACKAGE_NAME_BLOCKLIST_FILENAME = + "/product/etc/res/raw/content_protection/package_name_blocklist.txt"; + + private static final PackageInfo PACKAGE_INFO = new PackageInfo(); + + private static final List<String> LINES = + ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME); + + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager; + + private final List<String> mReadRawFiles = new ArrayList<>(); + + private ContentProtectionBlocklistManager mContentProtectionBlocklistManager; + + @Before + public void setup() { + mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager(); + } + + @Test + public void isAllowed_blocklistNotLoaded() { + boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME); + + assertThat(actual).isFalse(); + assertThat(mReadRawFiles).isEmpty(); + verifyZeroInteractions(mMockContentProtectionPackageManager); + } + + @Test + public void isAllowed_inBlocklist() { + mContentProtectionBlocklistManager.updateBlocklist(LINES.size()); + + boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME); + + assertThat(actual).isFalse(); + verifyZeroInteractions(mMockContentProtectionPackageManager); + } + + @Test + public void isAllowed_packageInfoNotFound() { + mContentProtectionBlocklistManager.updateBlocklist(LINES.size()); + when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME)) + .thenReturn(null); + + boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME); + + assertThat(actual).isFalse(); + verify(mMockContentProtectionPackageManager, never()) + .hasRequestedInternetPermissions(any()); + verify(mMockContentProtectionPackageManager, never()).isSystemApp(any()); + verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any()); + } + + @Test + public void isAllowed_notRequestedInternet() { + mContentProtectionBlocklistManager.updateBlocklist(LINES.size()); + when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME)) + .thenReturn(PACKAGE_INFO); + when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO)) + .thenReturn(false); + + boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME); + + assertThat(actual).isFalse(); + verify(mMockContentProtectionPackageManager, never()).isSystemApp(any()); + verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any()); + } + + @Test + public void isAllowed_systemApp() { + mContentProtectionBlocklistManager.updateBlocklist(LINES.size()); + when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME)) + .thenReturn(PACKAGE_INFO); + when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO)) + .thenReturn(true); + when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true); + + boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME); + + assertThat(actual).isFalse(); + verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any()); + } + + @Test + public void isAllowed_updatedSystemApp() { + mContentProtectionBlocklistManager.updateBlocklist(LINES.size()); + when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME)) + .thenReturn(PACKAGE_INFO); + when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO)) + .thenReturn(true); + when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true); + when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO)) + .thenReturn(true); + + boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME); + + assertThat(actual).isFalse(); + } + + @Test + public void isAllowed_allowed() { + mContentProtectionBlocklistManager.updateBlocklist(LINES.size()); + when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME)) + .thenReturn(PACKAGE_INFO); + when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO)) + .thenReturn(true); + when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false); + when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO)) + .thenReturn(false); + + boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME); + + assertThat(actual).isTrue(); + } + + @Test + public void updateBlocklist_negativeSize() { + mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1); + assertThat(mReadRawFiles).isEmpty(); + + mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME); + verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME); + } + + @Test + public void updateBlocklist_zeroSize() { + mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0); + assertThat(mReadRawFiles).isEmpty(); + + mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME); + verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME); + } + + @Test + public void updateBlocklist_positiveSize_belowTotal() { + mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1); + assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME); + + mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME); + mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME); + + verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME); + verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME); + } + + @Test + public void updateBlocklist_positiveSize_aboveTotal() { + mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1); + assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME); + + mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME); + mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME); + + verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME); + verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME); + } + + private final class TestContentProtectionBlocklistManager + extends ContentProtectionBlocklistManager { + + TestContentProtectionBlocklistManager() { + super(mMockContentProtectionPackageManager); + } + + @Override + protected List<String> readLinesFromRawFile(@NonNull String filename) { + mReadRawFiles.add(filename); + return LINES; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java index 2b49b8ab64d2..a242cdec89db 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java @@ -23,6 +23,8 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.USER_FRP; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; @@ -38,8 +40,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.nio.ByteBuffer; -/** Test setting a lockscreen credential and then verify it under USER_FRP */ +/** Tests that involve the Factory Reset Protection (FRP) credential. */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) @@ -148,4 +151,68 @@ public class LockscreenFrpTest extends BaseLockSettingsServiceTests { mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */) .getResponseCode()); } + + // The FRP block that gets written by the current version of Android must still be accepted by + // old versions of Android. This test tries to detect non-forward-compatible changes in + // PasswordData#toBytes(), which would break that. + @Test + public void testFrpBlock_isForwardsCompatible() { + mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID); + PersistentData data = mStorage.readPersistentDataBlock(); + ByteBuffer buffer = ByteBuffer.wrap(data.payload); + + final int credentialType = buffer.getInt(); + assertEquals(CREDENTIAL_TYPE_PIN, credentialType); + + final byte scryptLogN = buffer.get(); + assertTrue(scryptLogN >= 0); + + final byte scryptLogR = buffer.get(); + assertTrue(scryptLogR >= 0); + + final byte scryptLogP = buffer.get(); + assertTrue(scryptLogP >= 0); + + final int saltLength = buffer.getInt(); + assertTrue(saltLength > 0); + final byte[] salt = new byte[saltLength]; + buffer.get(salt); + + final int passwordHandleLength = buffer.getInt(); + assertTrue(passwordHandleLength > 0); + final byte[] passwordHandle = new byte[passwordHandleLength]; + buffer.get(passwordHandle); + } + + @Test + public void testFrpBlock_inBadAndroid14FormatIsAutomaticallyFixed() { + mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID); + + // Write a "bad" FRP block with PasswordData beginning with the bytes [0, 2]. + byte[] badPasswordData = new byte[] { + 0, 2, /* version 2 */ + 0, 3, /* CREDENTIAL_TYPE_PIN */ + 11, /* scryptLogN */ + 22, /* scryptLogR */ + 33, /* scryptLogP */ + 0, 0, 0, 5, /* salt.length */ + 1, 2, -1, -2, 55, /* salt */ + 0, 0, 0, 6, /* passwordHandle.length */ + 2, 3, -2, -3, 44, 1, /* passwordHandle */ + 0, 0, 0, 6, /* pinLength */ + }; + mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_GATEKEEPER, PRIMARY_USER_ID, 0, + badPasswordData); + + // Execute the code that should fix the FRP block. + assertFalse(mStorage.getBoolean("migrated_frp2", false, 0)); + mService.migrateOldDataAfterSystemReady(); + assertTrue(mStorage.getBoolean("migrated_frp2", false, 0)); + + // Verify that the FRP block has been fixed. + PersistentData data = mStorage.readPersistentDataBlock(); + assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type); + ByteBuffer buffer = ByteBuffer.wrap(data.payload); + assertEquals(CREDENTIAL_TYPE_PIN, buffer.getInt()); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index bfb6b0f1b6c7..ce0347dbe4ac 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -23,6 +23,7 @@ import static android.content.pm.UserInfo.FLAG_PRIMARY; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.PIN_LENGTH_UNAVAILABLE; import static org.junit.Assert.assertEquals; @@ -625,11 +626,9 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { } @Test - public void testPasswordDataV2VersionCredentialTypePin_deserialize() { - // Test that we can deserialize existing PasswordData and don't inadvertently change the - // wire format. + public void testDeserializePasswordData_forPinWithLengthAvailable() { byte[] serialized = new byte[] { - 0, 2, 0, 2, /* CREDENTIAL_TYPE_PASSWORD_OR_PIN */ + 0, 0, 0, 3, /* CREDENTIAL_TYPE_PIN */ 11, /* scryptLogN */ 22, /* scryptLogR */ 33, /* scryptLogP */ @@ -637,25 +636,24 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { 1, 2, -1, -2, 55, /* salt */ 0, 0, 0, 6, /* passwordHandle.length */ 2, 3, -2, -3, 44, 1, /* passwordHandle */ - 0, 0, 0, 5, /* pinLength */ + 0, 0, 0, 6, /* pinLength */ }; + assertFalse(PasswordData.isBadFormatFromAndroid14Beta(serialized)); PasswordData deserialized = PasswordData.fromBytes(serialized); assertEquals(11, deserialized.scryptLogN); assertEquals(22, deserialized.scryptLogR); assertEquals(33, deserialized.scryptLogP); - assertEquals(5, deserialized.pinLength); - assertEquals(CREDENTIAL_TYPE_PASSWORD_OR_PIN, deserialized.credentialType); + assertEquals(CREDENTIAL_TYPE_PIN, deserialized.credentialType); assertArrayEquals(PAYLOAD, deserialized.salt); assertArrayEquals(PAYLOAD2, deserialized.passwordHandle); + assertEquals(6, deserialized.pinLength); } @Test - public void testPasswordDataV2VersionNegativePinLengthNoCredential_deserialize() { - // Test that we can deserialize existing PasswordData and don't inadvertently change the - // wire format. + public void testDeserializePasswordData_forPinWithLengthExplicitlyUnavailable() { byte[] serialized = new byte[] { - 0, 2, -1, -1, /* CREDENTIAL_TYPE_NONE */ + 0, 0, 0, 3, /* CREDENTIAL_TYPE_PIN */ 11, /* scryptLogN */ 22, /* scryptLogR */ 33, /* scryptLogP */ @@ -663,23 +661,53 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { 1, 2, -1, -2, 55, /* salt */ 0, 0, 0, 6, /* passwordHandle.length */ 2, 3, -2, -3, 44, 1, /* passwordHandle */ - -1, -1, -1, -2, /* pinLength */ + -1, -1, -1, -1, /* pinLength */ }; PasswordData deserialized = PasswordData.fromBytes(serialized); assertEquals(11, deserialized.scryptLogN); assertEquals(22, deserialized.scryptLogR); assertEquals(33, deserialized.scryptLogP); - assertEquals(-2, deserialized.pinLength); - assertEquals(CREDENTIAL_TYPE_NONE, deserialized.credentialType); + assertEquals(CREDENTIAL_TYPE_PIN, deserialized.credentialType); assertArrayEquals(PAYLOAD, deserialized.salt); assertArrayEquals(PAYLOAD2, deserialized.passwordHandle); + assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength); } @Test - public void testPasswordDataV1VersionNoCredential_deserialize() { - // Test that we can deserialize existing PasswordData and don't inadvertently change the - // wire format. + public void testDeserializePasswordData_forPinWithVersionNumber() { + // Test deserializing a PasswordData that has a version number in the first two bytes. + // Files like this were created by some Android 14 beta versions. This version number was a + // mistake and should be ignored by the deserializer. + byte[] serialized = new byte[] { + 0, 2, /* version 2 */ + 0, 3, /* CREDENTIAL_TYPE_PIN */ + 11, /* scryptLogN */ + 22, /* scryptLogR */ + 33, /* scryptLogP */ + 0, 0, 0, 5, /* salt.length */ + 1, 2, -1, -2, 55, /* salt */ + 0, 0, 0, 6, /* passwordHandle.length */ + 2, 3, -2, -3, 44, 1, /* passwordHandle */ + 0, 0, 0, 6, /* pinLength */ + }; + assertTrue(PasswordData.isBadFormatFromAndroid14Beta(serialized)); + PasswordData deserialized = PasswordData.fromBytes(serialized); + + assertEquals(11, deserialized.scryptLogN); + assertEquals(22, deserialized.scryptLogR); + assertEquals(33, deserialized.scryptLogP); + assertEquals(CREDENTIAL_TYPE_PIN, deserialized.credentialType); + assertArrayEquals(PAYLOAD, deserialized.salt); + assertArrayEquals(PAYLOAD2, deserialized.passwordHandle); + assertEquals(6, deserialized.pinLength); + } + + @Test + public void testDeserializePasswordData_forNoneCred() { + // Test that a PasswordData that uses CREDENTIAL_TYPE_NONE and lacks the PIN length field + // can be deserialized. Files like this were created by Android 13 and earlier. Android 14 + // and later no longer create PasswordData for CREDENTIAL_TYPE_NONE. byte[] serialized = new byte[] { -1, -1, -1, -1, /* CREDENTIAL_TYPE_NONE */ 11, /* scryptLogN */ @@ -695,16 +723,17 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertEquals(11, deserialized.scryptLogN); assertEquals(22, deserialized.scryptLogR); assertEquals(33, deserialized.scryptLogP); - assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength); assertEquals(CREDENTIAL_TYPE_NONE, deserialized.credentialType); assertArrayEquals(PAYLOAD, deserialized.salt); assertArrayEquals(PAYLOAD2, deserialized.passwordHandle); + assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength); } @Test - public void testPasswordDataV1VersionCredentialTypePin_deserialize() { - // Test that we can deserialize existing PasswordData and don't inadvertently change the - // wire format. + public void testDeserializePasswordData_forPasswordOrPin() { + // Test that a PasswordData that uses CREDENTIAL_TYPE_PASSWORD_OR_PIN and lacks the PIN + // length field can be deserialized. Files like this were created by Android 10 and + // earlier. Android 11 eliminated CREDENTIAL_TYPE_PASSWORD_OR_PIN. byte[] serialized = new byte[] { 0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD_OR_PIN */ 11, /* scryptLogN */ @@ -720,10 +749,10 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertEquals(11, deserialized.scryptLogN); assertEquals(22, deserialized.scryptLogR); assertEquals(33, deserialized.scryptLogP); - assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength); assertEquals(CREDENTIAL_TYPE_PASSWORD_OR_PIN, deserialized.credentialType); assertArrayEquals(PAYLOAD, deserialized.salt); assertArrayEquals(PAYLOAD2, deserialized.passwordHandle); + assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength); } @Test diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java index ee4b839dda5a..b2dad7348525 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -23,6 +23,7 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE; import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -55,6 +56,7 @@ import com.android.internal.os.LongArrayMultiStateCounter; import com.android.internal.os.PowerProfile; import com.google.common.collect.ImmutableList; +import com.google.common.truth.LongSubject; import org.junit.Before; import org.junit.Test; @@ -77,6 +79,9 @@ public class BatteryStatsImplTest { private KernelSingleUidTimeReader mKernelSingleUidTimeReader; @Mock private PowerProfile mPowerProfile; + @Mock + private KernelWakelockReader mKernelWakelockReader; + private KernelWakelockStats mKernelWakelockStats = new KernelWakelockStats(); private final MockClock mMockClock = new MockClock(); private MockBatteryStatsImpl mBatteryStatsImpl; @@ -89,10 +94,13 @@ public class BatteryStatsImplTest { when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS); when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true); when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); + when(mKernelWakelockReader.readKernelWakelockStats( + any(KernelWakelockStats.class))).thenReturn(mKernelWakelockStats); mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock) .setPowerProfile(mPowerProfile) .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader) - .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader); + .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader) + .setKernelWakelockReader(mKernelWakelockReader); } @Test @@ -559,6 +567,48 @@ public class BatteryStatsImplTest { } @Test + public void kernelWakelocks() { + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + mKernelWakelockStats.put("lock1", new KernelWakelockStats.Entry(42, 1000, 314, 0)); + mKernelWakelockStats.put("lock2", new KernelWakelockStats.Entry(6, 2000, 0, 0)); + + mMockClock.realtime = 5000; + + // The fist call makes a snapshot of the initial state of the wakelocks + mBatteryStatsImpl.updateKernelWakelocksLocked(mMockClock.realtime * 1000); + + assertThat(mBatteryStatsImpl.getKernelWakelockStats()).hasSize(2); + + mMockClock.realtime += 2000; + + assertThatKernelWakelockTotalTime("lock1").isEqualTo(314); // active + assertThatKernelWakelockTotalTime("lock2").isEqualTo(0); // inactive + + mKernelWakelockStats.put("lock1", new KernelWakelockStats.Entry(43, 1100, 414, 0)); + mKernelWakelockStats.put("lock2", new KernelWakelockStats.Entry(6, 2222, 0, 0)); + + mMockClock.realtime += 3000; + + // Compute delta from the initial snapshot + mBatteryStatsImpl.updateKernelWakelocksLocked(mMockClock.realtime * 1000); + + mMockClock.realtime += 4000; + + assertThatKernelWakelockTotalTime("lock1").isEqualTo(414); + + // Wake lock not active. Expect relative total time as reported by Kernel: + // 2_222 - 2_000 = 222 + assertThatKernelWakelockTotalTime("lock2").isEqualTo(222); + } + + private LongSubject assertThatKernelWakelockTotalTime(String name) { + return assertWithMessage("Kernel wakelock " + name + " at " + mMockClock.realtime) + .that(mBatteryStatsImpl.getKernelWakelockStats().get(name) + .getTotalTimeLocked(mMockClock.realtime * 1000, 0)); + } + + @Test public void testGetBluetoothBatteryStats() { when(mPowerProfile.getAveragePower( PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index 6b21eb0ea729..e6454e4ce040 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java @@ -32,7 +32,6 @@ import static org.mockito.Mockito.mock; import android.app.ActivityManager; import android.app.usage.NetworkStatsManager; -import android.hardware.radio.V1_5.AccessNetwork; import android.os.BatteryStats; import android.os.BatteryStats.HistoryItem; import android.os.BatteryStats.Uid.Sensor; @@ -1727,27 +1726,38 @@ public class BatteryStatsNoteTest extends TestCase { } } - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UNKNOWN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.UNKNOWN, ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.GERAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.GERAN, ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UTRAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.UTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.EUTRAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.CDMA2000, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.IWLAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.IWLAN, ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_LOW, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MID, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH, new int[txLevelCount], 0)); - specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN, + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE, new int[txLevelCount], 0)); final ActivityStatsTechSpecificInfo[] specificInfos = specificInfoList.toArray( diff --git a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java index 70643d999a4e..c0f3c775ffe5 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java @@ -43,8 +43,13 @@ public class KernelWakelockReaderTest extends TestCase { } public ProcFileBuilder addLine(String name, int count, long timeMillis) { + return addLine(name, count, timeMillis, 0); + } + + public ProcFileBuilder addLine(String name, int count, long timeMillis, long activeTimeMs) { ensureHeader(); - mStringBuilder.append(name).append("\t").append(count).append("\t0\t0\t0\t0\t") + mStringBuilder.append(name).append("\t").append(count).append("\t0\t0\t0\t") + .append(activeTimeMs).append("\t") .append(timeMillis).append("\t0\t0\t0\n"); return this; } @@ -66,10 +71,19 @@ public class KernelWakelockReaderTest extends TestCase { * @return the created WakeLockInfo object. */ private WakeLockInfo createWakeLockInfo(String name, int activeCount, long totalTime) { + return createWakeLockInfo(name, activeCount, totalTime, 0); + } + + private WakeLockInfo createWakeLockInfo(String name, int activeCount, long totalTime, + long activeTimeMs) { WakeLockInfo info = new WakeLockInfo(); info.name = name; info.activeCount = activeCount; info.totalTime = totalTime; + info.activeTime = activeTimeMs; + if (activeTimeMs != 0) { + info.isActive = true; + } return info; } @@ -118,7 +132,7 @@ public class KernelWakelockReaderTest extends TestCase { @SmallTest public void testOneWakelock() throws Exception { byte[] buffer = new ProcFileBuilder() - .addLine("Wakelock", 34, 123) // Milliseconds + .addLine("Wakelock", 34, 123, 456) // Milliseconds .getBytes(); KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, @@ -129,8 +143,9 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("Wakelock")); KernelWakelockStats.Entry entry = staleStats.get("Wakelock"); - assertEquals(34, entry.mCount); - assertEquals(123 * 1000, entry.mTotalTime); // Microseconds + assertEquals(34, entry.count); + assertEquals(123 * 1000, entry.totalTimeUs); // Microseconds + assertEquals(456 * 1000, entry.activeTimeUs); // Microseconds } @SmallTest @@ -163,8 +178,8 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("Wakelock")); KernelWakelockStats.Entry entry = staleStats.get("Wakelock"); - assertEquals(2, entry.mCount); - assertEquals(20 * 1000, entry.mTotalTime); // Microseconds + assertEquals(2, entry.count); + assertEquals(20 * 1000, entry.totalTimeUs); // Microseconds } @SmallTest @@ -203,7 +218,7 @@ public class KernelWakelockReaderTest extends TestCase { @SmallTest public void testOneWakeLockInfo() { WakeLockInfo[] wlStats = new WakeLockInfo[1]; - wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000); // Milliseconds + wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000, 500); // Milliseconds KernelWakelockStats staleStats = mReader.updateWakelockStats(wlStats, new KernelWakelockStats()); @@ -213,8 +228,9 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("WakeLock")); KernelWakelockStats.Entry entry = staleStats.get("WakeLock"); - assertEquals(20, entry.mCount); - assertEquals(1000 * 1000, entry.mTotalTime); // Microseconds + assertEquals(20, entry.count); + assertEquals(1000 * 1000, entry.totalTimeUs); // Microseconds + assertEquals(500 * 1000, entry.activeTimeUs); // Microseconds } @SmallTest @@ -232,12 +248,12 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("WakeLock2")); KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1"); - assertEquals(10, entry1.mCount); - assertEquals(1000 * 1000, entry1.mTotalTime); // Microseconds + assertEquals(10, entry1.count); + assertEquals(1000 * 1000, entry1.totalTimeUs); // Microseconds KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2"); - assertEquals(20, entry2.mCount); - assertEquals(2000 * 1000, entry2.mTotalTime); // Microseconds + assertEquals(20, entry2.count); + assertEquals(2000 * 1000, entry2.totalTimeUs); // Microseconds } @SmallTest @@ -253,8 +269,8 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("WakeLock1")); KernelWakelockStats.Entry entry = staleStats.get("WakeLock1"); - assertEquals(10, entry.mCount); - assertEquals(1000 * 1000, entry.mTotalTime); // Microseconds + assertEquals(10, entry.count); + assertEquals(1000 * 1000, entry.totalTimeUs); // Microseconds wlStats[0] = createWakeLockInfo("WakeLock2", 20, 2000); // Milliseconds @@ -265,8 +281,8 @@ public class KernelWakelockReaderTest extends TestCase { assertFalse(staleStats.containsKey("WakeLock1")); assertTrue(staleStats.containsKey("WakeLock2")); entry = staleStats.get("WakeLock2"); - assertEquals(20, entry.mCount); - assertEquals(2000 * 1000, entry.mTotalTime); // Micro seconds + assertEquals(20, entry.count); + assertEquals(2000 * 1000, entry.totalTimeUs); // Micro seconds } // -------------------- Aggregate Wakelock Stats Tests -------------------- @@ -298,8 +314,8 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("Wakelock")); KernelWakelockStats.Entry entry = staleStats.get("Wakelock"); - assertEquals(34, entry.mCount); - assertEquals(1000 * 123, entry.mTotalTime); // Microseconds + assertEquals(34, entry.count); + assertEquals(1000 * 123, entry.totalTimeUs); // Microseconds } @SmallTest @@ -317,8 +333,8 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("WakeLock")); KernelWakelockStats.Entry entry = staleStats.get("WakeLock"); - assertEquals(10, entry.mCount); - assertEquals(1000 * 1000, entry.mTotalTime); // Microseconds + assertEquals(10, entry.count); + assertEquals(1000 * 1000, entry.totalTimeUs); // Microseconds } @SmallTest @@ -337,13 +353,13 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("WakeLock1")); KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1"); - assertEquals(34, entry1.mCount); - assertEquals(123 * 1000, entry1.mTotalTime); // Microseconds + assertEquals(34, entry1.count); + assertEquals(123 * 1000, entry1.totalTimeUs); // Microseconds assertTrue(staleStats.containsKey("WakeLock2")); KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2"); - assertEquals(10, entry2.mCount); - assertEquals(1000 * 1000, entry2.mTotalTime); // Microseconds + assertEquals(10, entry2.count); + assertEquals(1000 * 1000, entry2.totalTimeUs); // Microseconds } @SmallTest @@ -368,20 +384,20 @@ public class KernelWakelockReaderTest extends TestCase { assertTrue(staleStats.containsKey("WakeLock4")); KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1"); - assertEquals(34, entry1.mCount); - assertEquals(123 * 1000, entry1.mTotalTime); // Microseconds + assertEquals(34, entry1.count); + assertEquals(123 * 1000, entry1.totalTimeUs); // Microseconds KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2"); - assertEquals(46, entry2.mCount); - assertEquals(345 * 1000, entry2.mTotalTime); // Microseconds + assertEquals(46, entry2.count); + assertEquals(345 * 1000, entry2.totalTimeUs); // Microseconds KernelWakelockStats.Entry entry3 = staleStats.get("WakeLock3"); - assertEquals(10, entry3.mCount); - assertEquals(1000 * 1000, entry3.mTotalTime); // Microseconds + assertEquals(10, entry3.count); + assertEquals(1000 * 1000, entry3.totalTimeUs); // Microseconds KernelWakelockStats.Entry entry4 = staleStats.get("WakeLock4"); - assertEquals(20, entry4.mCount); - assertEquals(2000 * 1000, entry4.mTotalTime); // Microseconds + assertEquals(20, entry4.count); + assertEquals(2000 * 1000, entry4.totalTimeUs); // Microseconds buffer = new ProcFileBuilder() .addLine("WakeLock1", 45, 789) // Milliseconds @@ -401,11 +417,11 @@ public class KernelWakelockReaderTest extends TestCase { assertFalse(staleStats.containsKey("WakeLock3")); entry1 = staleStats.get("WakeLock1"); - assertEquals(45 + 56, entry1.mCount); - assertEquals((789 + 123) * 1000, entry1.mTotalTime); // Microseconds + assertEquals(45 + 56, entry1.count); + assertEquals((789 + 123) * 1000, entry1.totalTimeUs); // Microseconds entry2 = staleStats.get("WakeLock4"); - assertEquals(40, entry2.mCount); - assertEquals(4000 * 1000, entry4.mTotalTime); // Microseconds + assertEquals(40, entry2.count); + assertEquals(4000 * 1000, entry4.totalTimeUs); // Microseconds } } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java index 2e647c4ef78d..62e56f9eeed1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java @@ -27,7 +27,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.usage.NetworkStatsManager; -import android.hardware.radio.V1_5.AccessNetwork; import android.net.NetworkCapabilities; import android.net.NetworkStats; import android.os.BatteryConsumer; @@ -35,6 +34,7 @@ import android.os.BatteryStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; +import android.telephony.AccessNetworkConstants; import android.telephony.ActivityStatsTechSpecificInfo; import android.telephony.CellSignalStrength; import android.telephony.DataConnectionRealTimeInfo; @@ -248,22 +248,24 @@ public class MobileRadioPowerCalculatorTest { mStatsRule.setNetworkStats(networkStats); ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN, + AccessNetworkConstants.AccessNetworkType.CDMA2000, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[]{10, 11, 12, 13, 14}, 15); ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[]{20, 21, 22, 23, 24}, 25); ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_LOW, new int[]{30, 31, 32, 33, 34}, 35); ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MID, new int[]{40, 41, 42, 43, 44}, 45); ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH, new int[]{50, 51, 52, 53, 54}, 55); ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE, new int[]{60, 61, 62, 63, 64}, 65); ActivityStatsTechSpecificInfo[] ratInfos = @@ -719,22 +721,24 @@ public class MobileRadioPowerCalculatorTest { mStatsRule.setNetworkStats(networkStats); ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN, + AccessNetworkConstants.AccessNetworkType.CDMA2000, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[]{10, 11, 12, 13, 14}, 15); ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[]{20, 21, 22, 23, 24}, 25); ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_LOW, new int[]{30, 31, 32, 33, 34}, 35); ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MID, new int[]{40, 41, 42, 43, 44}, 45); ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH, new int[]{50, 51, 52, 53, 54}, 55); ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo( - AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE, + AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE, new int[]{60, 61, 62, 63, 64}, 65); ActivityStatsTechSpecificInfo[] ratInfos = diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index d78ab8677800..b032cbe67485 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -74,6 +74,7 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { mCpuUidFreqTimeReader = mock(KernelCpuUidFreqTimeReader.class); when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200}); + mKernelWakelockReader = null; } public void initMeasuredEnergyStats(String[] customBucketNames) { @@ -173,6 +174,11 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return this; } + public MockBatteryStatsImpl setKernelWakelockReader(KernelWakelockReader reader) { + mKernelWakelockReader = reader; + return this; + } + public MockBatteryStatsImpl setSystemServerCpuThreadReader( SystemServerCpuThreadReader systemServerCpuThreadReader) { mSystemServerCpuThreadReader = systemServerCpuThreadReader; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index 27677e153d83..0e627b2f0909 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -88,7 +88,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { private static final Multimap<Class<?>, String> KNOWN_BAD = ImmutableMultimap.<Class<?>, String>builder() .put(Notification.Builder.class, "setPublicVersion") // b/276294099 - .putAll(RemoteViews.class, "addView", "addStableView") // b/277740082 .put(RemoteViews.class, "setIcon") // b/281018094 .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385 .put(Person.Builder.class, "setUri") // TODO: b/281044385 diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 2671e771aa59..2b589bf59682 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -944,7 +944,7 @@ public class ActivityStarterTests extends WindowTestsBase { anyInt(), anyInt())); doReturn(BackgroundStartPrivileges.allowBackgroundActivityStarts(null)).when( () -> PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller( - anyObject(), anyInt())); + anyObject(), anyInt(), anyObject())); runAndVerifyBackgroundActivityStartsSubtest( "allowed_notAborted", false, UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 5ec36048234b..a422c6893de5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -178,6 +178,44 @@ public class DisplayPolicyTests extends WindowTestsBase { dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); } + @Test + public void testChooseNavigationBackgroundWindow() { + final WindowState drawBarWin = createOpaqueFullscreen(false); + final WindowState nonDrawBarWin = createDimmingDialogWindow(true); + + final WindowState visibleIme = createInputMethodWindow(true, true, false); + final WindowState invisibleIme = createInputMethodWindow(false, true, false); + final WindowState nonDrawBarIme = createInputMethodWindow(true, false, false); + + assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow( + drawBarWin, null, NAV_BAR_BOTTOM)); + assertNull(DisplayPolicy.chooseNavigationBackgroundWindow( + null, null, NAV_BAR_BOTTOM)); + assertNull(DisplayPolicy.chooseNavigationBackgroundWindow( + nonDrawBarWin, null, NAV_BAR_BOTTOM)); + + assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow( + drawBarWin, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow( + null, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow( + nonDrawBarWin, visibleIme, NAV_BAR_BOTTOM)); + + assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow( + drawBarWin, invisibleIme, NAV_BAR_BOTTOM)); + assertNull(DisplayPolicy.chooseNavigationBackgroundWindow( + null, invisibleIme, NAV_BAR_BOTTOM)); + assertNull(DisplayPolicy.chooseNavigationBackgroundWindow( + nonDrawBarWin, invisibleIme, NAV_BAR_BOTTOM)); + + assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow( + drawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM)); + assertNull(DisplayPolicy.chooseNavigationBackgroundWindow( + null, nonDrawBarIme, NAV_BAR_BOTTOM)); + assertNull(DisplayPolicy.chooseNavigationBackgroundWindow( + nonDrawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM)); + } + @SetupWindows(addWindows = W_NAVIGATION_BAR) @Test public void testUpdateLightNavigationBarLw() { diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 7d507e9150e8..34a13bfa855c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -38,6 +38,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCA import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; @@ -461,7 +462,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() { final InsetsSource taskbar = new InsetsSource(/*id=*/ 0, WindowInsets.Type.navigationBars()); - taskbar.setInsetsRoundedCornerFrame(true); + taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER); final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar); final Rect opaqueBounds = new Rect(0, 0, 500, 300); doReturn(opaqueBounds).when(mActivity).getBounds(); @@ -505,7 +506,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testGetCropBoundsIfNeeded_appliesCrop() { final InsetsSource taskbar = new InsetsSource(/*id=*/ 0, WindowInsets.Type.navigationBars()); - taskbar.setInsetsRoundedCornerFrame(true); + taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER); final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar); // Apply crop if taskbar is expanded @@ -528,7 +529,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() { final InsetsSource taskbar = new InsetsSource(/*id=*/ 0, WindowInsets.Type.navigationBars()); - taskbar.setInsetsRoundedCornerFrame(true); + taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER); final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar); final float scaling = 2.0f; 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 2dd34eb5ac4d..d91be16f2538 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -30,6 +30,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 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; +import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; @@ -3483,7 +3484,7 @@ public class SizeCompatTests extends WindowTestsBase { final InsetsSource navSource = new InsetsSource( InsetsSource.createId(null, 0, navigationBars()), navigationBars()); - navSource.setInsetsRoundedCornerFrame(true); + navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER); navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight)); mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15); @@ -3531,7 +3532,7 @@ public class SizeCompatTests extends WindowTestsBase { final InsetsSource navSource = new InsetsSource( InsetsSource.createId(null, 0, navigationBars()), navigationBars()); - navSource.setInsetsRoundedCornerFrame(true); + navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER); // Immersive activity has transient navbar navSource.setVisible(!immersive); navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight)); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index 31ab3c853c07..ffc7b8eb13d6 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -84,6 +84,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.infra.AndroidFuture; import com.android.server.LocalServices; +import com.android.server.policy.AppOpsPolicy; import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener; import java.io.Closeable; @@ -743,18 +744,24 @@ abstract class DetectorSession { void enforcePermissionsForDataDelivery() { Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { - int result = PermissionChecker.checkPermissionForPreflight( - mContext, RECORD_AUDIO, /* pid */ -1, mVoiceInteractorIdentity.uid, - mVoiceInteractorIdentity.packageName); - if (result != PermissionChecker.PERMISSION_GRANTED) { - throw new SecurityException( - "Failed to obtain permission RECORD_AUDIO for identity " - + mVoiceInteractorIdentity); + if (AppOpsPolicy.isHotwordDetectionServiceRequired(mContext.getPackageManager())) { + int result = PermissionChecker.checkPermissionForPreflight( + mContext, RECORD_AUDIO, /* pid */ -1, mVoiceInteractorIdentity.uid, + mVoiceInteractorIdentity.packageName); + if (result != PermissionChecker.PERMISSION_GRANTED) { + throw new SecurityException( + "Failed to obtain permission RECORD_AUDIO for identity " + + mVoiceInteractorIdentity); + } + int hotwordOp = AppOpsManager.strOpToOp( + AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD); + mAppOpsManager.noteOpNoThrow(hotwordOp, + mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, + mVoiceInteractorIdentity.attributionTag, HOTWORD_DETECTION_OP_MESSAGE); + } else { + enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity, + RECORD_AUDIO, HOTWORD_DETECTION_OP_MESSAGE); } - int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD); - mAppOpsManager.noteOpNoThrow(hotwordOp, - mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, - mVoiceInteractorIdentity.attributionTag, HOTWORD_DETECTION_OP_MESSAGE); enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity, CAPTURE_AUDIO_HOTWORD, HOTWORD_DETECTION_OP_MESSAGE); } diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java index 27ba67667d92..35721f11c27f 100644 --- a/telephony/java/android/telephony/AccessNetworkConstants.java +++ b/telephony/java/android/telephony/AccessNetworkConstants.java @@ -19,7 +19,7 @@ package android.telephony; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; -import android.hardware.radio.V1_5.AccessNetwork; +import android.hardware.radio.AccessNetwork; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -134,20 +134,22 @@ public final class AccessNetworkConstants { * http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf */ public static final class GeranBand { - public static final int BAND_T380 = android.hardware.radio.V1_1.GeranBands.BAND_T380; - public static final int BAND_T410 = android.hardware.radio.V1_1.GeranBands.BAND_T410; - public static final int BAND_450 = android.hardware.radio.V1_1.GeranBands.BAND_450; - public static final int BAND_480 = android.hardware.radio.V1_1.GeranBands.BAND_480; - public static final int BAND_710 = android.hardware.radio.V1_1.GeranBands.BAND_710; - public static final int BAND_750 = android.hardware.radio.V1_1.GeranBands.BAND_750; - public static final int BAND_T810 = android.hardware.radio.V1_1.GeranBands.BAND_T810; - public static final int BAND_850 = android.hardware.radio.V1_1.GeranBands.BAND_850; - public static final int BAND_P900 = android.hardware.radio.V1_1.GeranBands.BAND_P900; - public static final int BAND_E900 = android.hardware.radio.V1_1.GeranBands.BAND_E900; - public static final int BAND_R900 = android.hardware.radio.V1_1.GeranBands.BAND_R900; - public static final int BAND_DCS1800 = android.hardware.radio.V1_1.GeranBands.BAND_DCS1800; - public static final int BAND_PCS1900 = android.hardware.radio.V1_1.GeranBands.BAND_PCS1900; - public static final int BAND_ER900 = android.hardware.radio.V1_1.GeranBands.BAND_ER900; + public static final int BAND_T380 = android.hardware.radio.network.GeranBands.BAND_T380; + public static final int BAND_T410 = android.hardware.radio.network.GeranBands.BAND_T410; + public static final int BAND_450 = android.hardware.radio.network.GeranBands.BAND_450; + public static final int BAND_480 = android.hardware.radio.network.GeranBands.BAND_480; + public static final int BAND_710 = android.hardware.radio.network.GeranBands.BAND_710; + public static final int BAND_750 = android.hardware.radio.network.GeranBands.BAND_750; + public static final int BAND_T810 = android.hardware.radio.network.GeranBands.BAND_T810; + public static final int BAND_850 = android.hardware.radio.network.GeranBands.BAND_850; + public static final int BAND_P900 = android.hardware.radio.network.GeranBands.BAND_P900; + public static final int BAND_E900 = android.hardware.radio.network.GeranBands.BAND_E900; + public static final int BAND_R900 = android.hardware.radio.network.GeranBands.BAND_R900; + public static final int BAND_DCS1800 = + android.hardware.radio.network.GeranBands.BAND_DCS1800; + public static final int BAND_PCS1900 = + android.hardware.radio.network.GeranBands.BAND_PCS1900; + public static final int BAND_ER900 = android.hardware.radio.network.GeranBands.BAND_ER900; /** * GeranBand @@ -226,28 +228,28 @@ public final class AccessNetworkConstants { * http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf */ public static final class UtranBand { - public static final int BAND_1 = android.hardware.radio.V1_5.UtranBands.BAND_1; - public static final int BAND_2 = android.hardware.radio.V1_5.UtranBands.BAND_2; - public static final int BAND_3 = android.hardware.radio.V1_5.UtranBands.BAND_3; - public static final int BAND_4 = android.hardware.radio.V1_5.UtranBands.BAND_4; - public static final int BAND_5 = android.hardware.radio.V1_5.UtranBands.BAND_5; - public static final int BAND_6 = android.hardware.radio.V1_5.UtranBands.BAND_6; - public static final int BAND_7 = android.hardware.radio.V1_5.UtranBands.BAND_7; - public static final int BAND_8 = android.hardware.radio.V1_5.UtranBands.BAND_8; - public static final int BAND_9 = android.hardware.radio.V1_5.UtranBands.BAND_9; - public static final int BAND_10 = android.hardware.radio.V1_5.UtranBands.BAND_10; - public static final int BAND_11 = android.hardware.radio.V1_5.UtranBands.BAND_11; - public static final int BAND_12 = android.hardware.radio.V1_5.UtranBands.BAND_12; - public static final int BAND_13 = android.hardware.radio.V1_5.UtranBands.BAND_13; - public static final int BAND_14 = android.hardware.radio.V1_5.UtranBands.BAND_14; + public static final int BAND_1 = android.hardware.radio.network.UtranBands.BAND_1; + public static final int BAND_2 = android.hardware.radio.network.UtranBands.BAND_2; + public static final int BAND_3 = android.hardware.radio.network.UtranBands.BAND_3; + public static final int BAND_4 = android.hardware.radio.network.UtranBands.BAND_4; + public static final int BAND_5 = android.hardware.radio.network.UtranBands.BAND_5; + public static final int BAND_6 = android.hardware.radio.network.UtranBands.BAND_6; + public static final int BAND_7 = android.hardware.radio.network.UtranBands.BAND_7; + public static final int BAND_8 = android.hardware.radio.network.UtranBands.BAND_8; + public static final int BAND_9 = android.hardware.radio.network.UtranBands.BAND_9; + public static final int BAND_10 = android.hardware.radio.network.UtranBands.BAND_10; + public static final int BAND_11 = android.hardware.radio.network.UtranBands.BAND_11; + public static final int BAND_12 = android.hardware.radio.network.UtranBands.BAND_12; + public static final int BAND_13 = android.hardware.radio.network.UtranBands.BAND_13; + public static final int BAND_14 = android.hardware.radio.network.UtranBands.BAND_14; // band 15, 16, 17, 18 are reserved - public static final int BAND_19 = android.hardware.radio.V1_5.UtranBands.BAND_19; - public static final int BAND_20 = android.hardware.radio.V1_5.UtranBands.BAND_20; - public static final int BAND_21 = android.hardware.radio.V1_5.UtranBands.BAND_21; - public static final int BAND_22 = android.hardware.radio.V1_5.UtranBands.BAND_22; + public static final int BAND_19 = android.hardware.radio.network.UtranBands.BAND_19; + public static final int BAND_20 = android.hardware.radio.network.UtranBands.BAND_20; + public static final int BAND_21 = android.hardware.radio.network.UtranBands.BAND_21; + public static final int BAND_22 = android.hardware.radio.network.UtranBands.BAND_22; // band 23, 24 are reserved - public static final int BAND_25 = android.hardware.radio.V1_5.UtranBands.BAND_25; - public static final int BAND_26 = android.hardware.radio.V1_5.UtranBands.BAND_26; + public static final int BAND_25 = android.hardware.radio.network.UtranBands.BAND_25; + public static final int BAND_26 = android.hardware.radio.network.UtranBands.BAND_26; // Frequency bands for TD-SCDMA. Defined in 3GPP TS 25.102, Table 5.2. @@ -256,38 +258,38 @@ public final class AccessNetworkConstants { * 1900 - 1920 MHz: Uplink and downlink transmission * 2010 - 2025 MHz: Uplink and downlink transmission */ - public static final int BAND_A = android.hardware.radio.V1_5.UtranBands.BAND_A; + public static final int BAND_A = android.hardware.radio.network.UtranBands.BAND_A; /** * Band B * 1850 - 1910 MHz: Uplink and downlink transmission * 1930 - 1990 MHz: Uplink and downlink transmission */ - public static final int BAND_B = android.hardware.radio.V1_5.UtranBands.BAND_B; + public static final int BAND_B = android.hardware.radio.network.UtranBands.BAND_B; /** * Band C * 1910 - 1930 MHz: Uplink and downlink transmission */ - public static final int BAND_C = android.hardware.radio.V1_5.UtranBands.BAND_C; + public static final int BAND_C = android.hardware.radio.network.UtranBands.BAND_C; /** * Band D * 2570 - 2620 MHz: Uplink and downlink transmission */ - public static final int BAND_D = android.hardware.radio.V1_5.UtranBands.BAND_D; + public static final int BAND_D = android.hardware.radio.network.UtranBands.BAND_D; /** * Band E * 2300—2400 MHz: Uplink and downlink transmission */ - public static final int BAND_E = android.hardware.radio.V1_5.UtranBands.BAND_E; + public static final int BAND_E = android.hardware.radio.network.UtranBands.BAND_E; /** * Band F * 1880 - 1920 MHz: Uplink and downlink transmission */ - public static final int BAND_F = android.hardware.radio.V1_5.UtranBands.BAND_F; + public static final int BAND_F = android.hardware.radio.network.UtranBands.BAND_F; /** * UtranBand @@ -389,66 +391,66 @@ public final class AccessNetworkConstants { * https://www.etsi.org/deliver/etsi_ts/136100_136199/136101/15.09.00_60/ts_136101v150900p.pdf */ public static final class EutranBand { - public static final int BAND_1 = android.hardware.radio.V1_5.EutranBands.BAND_1; - public static final int BAND_2 = android.hardware.radio.V1_5.EutranBands.BAND_2; - public static final int BAND_3 = android.hardware.radio.V1_5.EutranBands.BAND_3; - public static final int BAND_4 = android.hardware.radio.V1_5.EutranBands.BAND_4; - public static final int BAND_5 = android.hardware.radio.V1_5.EutranBands.BAND_5; - public static final int BAND_6 = android.hardware.radio.V1_5.EutranBands.BAND_6; - public static final int BAND_7 = android.hardware.radio.V1_5.EutranBands.BAND_7; - public static final int BAND_8 = android.hardware.radio.V1_5.EutranBands.BAND_8; - public static final int BAND_9 = android.hardware.radio.V1_5.EutranBands.BAND_9; - public static final int BAND_10 = android.hardware.radio.V1_5.EutranBands.BAND_10; - public static final int BAND_11 = android.hardware.radio.V1_5.EutranBands.BAND_11; - public static final int BAND_12 = android.hardware.radio.V1_5.EutranBands.BAND_12; - public static final int BAND_13 = android.hardware.radio.V1_5.EutranBands.BAND_13; - public static final int BAND_14 = android.hardware.radio.V1_5.EutranBands.BAND_14; - public static final int BAND_17 = android.hardware.radio.V1_5.EutranBands.BAND_17; - public static final int BAND_18 = android.hardware.radio.V1_5.EutranBands.BAND_18; - public static final int BAND_19 = android.hardware.radio.V1_5.EutranBands.BAND_19; - public static final int BAND_20 = android.hardware.radio.V1_5.EutranBands.BAND_20; - public static final int BAND_21 = android.hardware.radio.V1_5.EutranBands.BAND_21; - public static final int BAND_22 = android.hardware.radio.V1_5.EutranBands.BAND_22; - public static final int BAND_23 = android.hardware.radio.V1_5.EutranBands.BAND_23; - public static final int BAND_24 = android.hardware.radio.V1_5.EutranBands.BAND_24; - public static final int BAND_25 = android.hardware.radio.V1_5.EutranBands.BAND_25; - public static final int BAND_26 = android.hardware.radio.V1_5.EutranBands.BAND_26; - public static final int BAND_27 = android.hardware.radio.V1_5.EutranBands.BAND_27; - public static final int BAND_28 = android.hardware.radio.V1_5.EutranBands.BAND_28; - public static final int BAND_30 = android.hardware.radio.V1_5.EutranBands.BAND_30; - public static final int BAND_31 = android.hardware.radio.V1_5.EutranBands.BAND_31; - public static final int BAND_33 = android.hardware.radio.V1_5.EutranBands.BAND_33; - public static final int BAND_34 = android.hardware.radio.V1_5.EutranBands.BAND_34; - public static final int BAND_35 = android.hardware.radio.V1_5.EutranBands.BAND_35; - public static final int BAND_36 = android.hardware.radio.V1_5.EutranBands.BAND_36; - public static final int BAND_37 = android.hardware.radio.V1_5.EutranBands.BAND_37; - public static final int BAND_38 = android.hardware.radio.V1_5.EutranBands.BAND_38; - public static final int BAND_39 = android.hardware.radio.V1_5.EutranBands.BAND_39; - public static final int BAND_40 = android.hardware.radio.V1_5.EutranBands.BAND_40; - public static final int BAND_41 = android.hardware.radio.V1_5.EutranBands.BAND_41; - public static final int BAND_42 = android.hardware.radio.V1_5.EutranBands.BAND_42; - public static final int BAND_43 = android.hardware.radio.V1_5.EutranBands.BAND_43; - public static final int BAND_44 = android.hardware.radio.V1_5.EutranBands.BAND_44; - public static final int BAND_45 = android.hardware.radio.V1_5.EutranBands.BAND_45; - public static final int BAND_46 = android.hardware.radio.V1_5.EutranBands.BAND_46; - public static final int BAND_47 = android.hardware.radio.V1_5.EutranBands.BAND_47; - public static final int BAND_48 = android.hardware.radio.V1_5.EutranBands.BAND_48; - public static final int BAND_49 = android.hardware.radio.V1_5.EutranBands.BAND_49; - public static final int BAND_50 = android.hardware.radio.V1_5.EutranBands.BAND_50; - public static final int BAND_51 = android.hardware.radio.V1_5.EutranBands.BAND_51; - public static final int BAND_52 = android.hardware.radio.V1_5.EutranBands.BAND_52; - public static final int BAND_53 = android.hardware.radio.V1_5.EutranBands.BAND_53; - public static final int BAND_65 = android.hardware.radio.V1_5.EutranBands.BAND_65; - public static final int BAND_66 = android.hardware.radio.V1_5.EutranBands.BAND_66; - public static final int BAND_68 = android.hardware.radio.V1_5.EutranBands.BAND_68; - public static final int BAND_70 = android.hardware.radio.V1_5.EutranBands.BAND_70; - public static final int BAND_71 = android.hardware.radio.V1_5.EutranBands.BAND_71; - public static final int BAND_72 = android.hardware.radio.V1_5.EutranBands.BAND_72; - public static final int BAND_73 = android.hardware.radio.V1_5.EutranBands.BAND_73; - public static final int BAND_74 = android.hardware.radio.V1_5.EutranBands.BAND_74; - public static final int BAND_85 = android.hardware.radio.V1_5.EutranBands.BAND_85; - public static final int BAND_87 = android.hardware.radio.V1_5.EutranBands.BAND_87; - public static final int BAND_88 = android.hardware.radio.V1_5.EutranBands.BAND_88; + public static final int BAND_1 = android.hardware.radio.network.EutranBands.BAND_1; + public static final int BAND_2 = android.hardware.radio.network.EutranBands.BAND_2; + public static final int BAND_3 = android.hardware.radio.network.EutranBands.BAND_3; + public static final int BAND_4 = android.hardware.radio.network.EutranBands.BAND_4; + public static final int BAND_5 = android.hardware.radio.network.EutranBands.BAND_5; + public static final int BAND_6 = android.hardware.radio.network.EutranBands.BAND_6; + public static final int BAND_7 = android.hardware.radio.network.EutranBands.BAND_7; + public static final int BAND_8 = android.hardware.radio.network.EutranBands.BAND_8; + public static final int BAND_9 = android.hardware.radio.network.EutranBands.BAND_9; + public static final int BAND_10 = android.hardware.radio.network.EutranBands.BAND_10; + public static final int BAND_11 = android.hardware.radio.network.EutranBands.BAND_11; + public static final int BAND_12 = android.hardware.radio.network.EutranBands.BAND_12; + public static final int BAND_13 = android.hardware.radio.network.EutranBands.BAND_13; + public static final int BAND_14 = android.hardware.radio.network.EutranBands.BAND_14; + public static final int BAND_17 = android.hardware.radio.network.EutranBands.BAND_17; + public static final int BAND_18 = android.hardware.radio.network.EutranBands.BAND_18; + public static final int BAND_19 = android.hardware.radio.network.EutranBands.BAND_19; + public static final int BAND_20 = android.hardware.radio.network.EutranBands.BAND_20; + public static final int BAND_21 = android.hardware.radio.network.EutranBands.BAND_21; + public static final int BAND_22 = android.hardware.radio.network.EutranBands.BAND_22; + public static final int BAND_23 = android.hardware.radio.network.EutranBands.BAND_23; + public static final int BAND_24 = android.hardware.radio.network.EutranBands.BAND_24; + public static final int BAND_25 = android.hardware.radio.network.EutranBands.BAND_25; + public static final int BAND_26 = android.hardware.radio.network.EutranBands.BAND_26; + public static final int BAND_27 = android.hardware.radio.network.EutranBands.BAND_27; + public static final int BAND_28 = android.hardware.radio.network.EutranBands.BAND_28; + public static final int BAND_30 = android.hardware.radio.network.EutranBands.BAND_30; + public static final int BAND_31 = android.hardware.radio.network.EutranBands.BAND_31; + public static final int BAND_33 = android.hardware.radio.network.EutranBands.BAND_33; + public static final int BAND_34 = android.hardware.radio.network.EutranBands.BAND_34; + public static final int BAND_35 = android.hardware.radio.network.EutranBands.BAND_35; + public static final int BAND_36 = android.hardware.radio.network.EutranBands.BAND_36; + public static final int BAND_37 = android.hardware.radio.network.EutranBands.BAND_37; + public static final int BAND_38 = android.hardware.radio.network.EutranBands.BAND_38; + public static final int BAND_39 = android.hardware.radio.network.EutranBands.BAND_39; + public static final int BAND_40 = android.hardware.radio.network.EutranBands.BAND_40; + public static final int BAND_41 = android.hardware.radio.network.EutranBands.BAND_41; + public static final int BAND_42 = android.hardware.radio.network.EutranBands.BAND_42; + public static final int BAND_43 = android.hardware.radio.network.EutranBands.BAND_43; + public static final int BAND_44 = android.hardware.radio.network.EutranBands.BAND_44; + public static final int BAND_45 = android.hardware.radio.network.EutranBands.BAND_45; + public static final int BAND_46 = android.hardware.radio.network.EutranBands.BAND_46; + public static final int BAND_47 = android.hardware.radio.network.EutranBands.BAND_47; + public static final int BAND_48 = android.hardware.radio.network.EutranBands.BAND_48; + public static final int BAND_49 = android.hardware.radio.network.EutranBands.BAND_49; + public static final int BAND_50 = android.hardware.radio.network.EutranBands.BAND_50; + public static final int BAND_51 = android.hardware.radio.network.EutranBands.BAND_51; + public static final int BAND_52 = android.hardware.radio.network.EutranBands.BAND_52; + public static final int BAND_53 = android.hardware.radio.network.EutranBands.BAND_53; + public static final int BAND_65 = android.hardware.radio.network.EutranBands.BAND_65; + public static final int BAND_66 = android.hardware.radio.network.EutranBands.BAND_66; + public static final int BAND_68 = android.hardware.radio.network.EutranBands.BAND_68; + public static final int BAND_70 = android.hardware.radio.network.EutranBands.BAND_70; + public static final int BAND_71 = android.hardware.radio.network.EutranBands.BAND_71; + public static final int BAND_72 = android.hardware.radio.network.EutranBands.BAND_72; + public static final int BAND_73 = android.hardware.radio.network.EutranBands.BAND_73; + public static final int BAND_74 = android.hardware.radio.network.EutranBands.BAND_74; + public static final int BAND_85 = android.hardware.radio.network.EutranBands.BAND_85; + public static final int BAND_87 = android.hardware.radio.network.EutranBands.BAND_87; + public static final int BAND_88 = android.hardware.radio.network.EutranBands.BAND_88; /** * EutranBands @@ -714,61 +716,61 @@ public final class AccessNetworkConstants { */ public static final class NgranBands { /** 3GPP TS 38.101-1, Version 16.5.0, Table 5.2-1: FR1 bands */ - public static final int BAND_1 = android.hardware.radio.V1_5.NgranBands.BAND_1; - public static final int BAND_2 = android.hardware.radio.V1_5.NgranBands.BAND_2; - public static final int BAND_3 = android.hardware.radio.V1_5.NgranBands.BAND_3; - public static final int BAND_5 = android.hardware.radio.V1_5.NgranBands.BAND_5; - public static final int BAND_7 = android.hardware.radio.V1_5.NgranBands.BAND_7; - public static final int BAND_8 = android.hardware.radio.V1_5.NgranBands.BAND_8; - public static final int BAND_12 = android.hardware.radio.V1_5.NgranBands.BAND_12; - public static final int BAND_14 = android.hardware.radio.V1_5.NgranBands.BAND_14; - public static final int BAND_18 = android.hardware.radio.V1_5.NgranBands.BAND_18; - public static final int BAND_20 = android.hardware.radio.V1_5.NgranBands.BAND_20; - public static final int BAND_25 = android.hardware.radio.V1_5.NgranBands.BAND_25; - public static final int BAND_26 = android.hardware.radio.V1_6.NgranBands.BAND_26; - public static final int BAND_28 = android.hardware.radio.V1_5.NgranBands.BAND_28; - public static final int BAND_29 = android.hardware.radio.V1_5.NgranBands.BAND_29; - public static final int BAND_30 = android.hardware.radio.V1_5.NgranBands.BAND_30; - public static final int BAND_34 = android.hardware.radio.V1_5.NgranBands.BAND_34; - public static final int BAND_38 = android.hardware.radio.V1_5.NgranBands.BAND_38; - public static final int BAND_39 = android.hardware.radio.V1_5.NgranBands.BAND_39; - public static final int BAND_40 = android.hardware.radio.V1_5.NgranBands.BAND_40; - public static final int BAND_41 = android.hardware.radio.V1_5.NgranBands.BAND_41; - public static final int BAND_46 = android.hardware.radio.V1_6.NgranBands.BAND_46; - public static final int BAND_48 = android.hardware.radio.V1_5.NgranBands.BAND_48; - public static final int BAND_50 = android.hardware.radio.V1_5.NgranBands.BAND_50; - public static final int BAND_51 = android.hardware.radio.V1_5.NgranBands.BAND_51; - public static final int BAND_53 = android.hardware.radio.V1_6.NgranBands.BAND_53; - public static final int BAND_65 = android.hardware.radio.V1_5.NgranBands.BAND_65; - public static final int BAND_66 = android.hardware.radio.V1_5.NgranBands.BAND_66; - public static final int BAND_70 = android.hardware.radio.V1_5.NgranBands.BAND_70; - public static final int BAND_71 = android.hardware.radio.V1_5.NgranBands.BAND_71; - public static final int BAND_74 = android.hardware.radio.V1_5.NgranBands.BAND_74; - public static final int BAND_75 = android.hardware.radio.V1_5.NgranBands.BAND_75; - public static final int BAND_76 = android.hardware.radio.V1_5.NgranBands.BAND_76; - public static final int BAND_77 = android.hardware.radio.V1_5.NgranBands.BAND_77; - public static final int BAND_78 = android.hardware.radio.V1_5.NgranBands.BAND_78; - public static final int BAND_79 = android.hardware.radio.V1_5.NgranBands.BAND_79; - public static final int BAND_80 = android.hardware.radio.V1_5.NgranBands.BAND_80; - public static final int BAND_81 = android.hardware.radio.V1_5.NgranBands.BAND_81; - public static final int BAND_82 = android.hardware.radio.V1_5.NgranBands.BAND_82; - public static final int BAND_83 = android.hardware.radio.V1_5.NgranBands.BAND_83; - public static final int BAND_84 = android.hardware.radio.V1_5.NgranBands.BAND_84; - public static final int BAND_86 = android.hardware.radio.V1_5.NgranBands.BAND_86; - public static final int BAND_89 = android.hardware.radio.V1_5.NgranBands.BAND_89; - public static final int BAND_90 = android.hardware.radio.V1_5.NgranBands.BAND_90; - public static final int BAND_91 = android.hardware.radio.V1_5.NgranBands.BAND_91; - public static final int BAND_92 = android.hardware.radio.V1_5.NgranBands.BAND_92; - public static final int BAND_93 = android.hardware.radio.V1_5.NgranBands.BAND_93; - public static final int BAND_94 = android.hardware.radio.V1_5.NgranBands.BAND_94; - public static final int BAND_95 = android.hardware.radio.V1_5.NgranBands.BAND_95; - public static final int BAND_96 = android.hardware.radio.V1_6.NgranBands.BAND_96; + public static final int BAND_1 = android.hardware.radio.network.NgranBands.BAND_1; + public static final int BAND_2 = android.hardware.radio.network.NgranBands.BAND_2; + public static final int BAND_3 = android.hardware.radio.network.NgranBands.BAND_3; + public static final int BAND_5 = android.hardware.radio.network.NgranBands.BAND_5; + public static final int BAND_7 = android.hardware.radio.network.NgranBands.BAND_7; + public static final int BAND_8 = android.hardware.radio.network.NgranBands.BAND_8; + public static final int BAND_12 = android.hardware.radio.network.NgranBands.BAND_12; + public static final int BAND_14 = android.hardware.radio.network.NgranBands.BAND_14; + public static final int BAND_18 = android.hardware.radio.network.NgranBands.BAND_18; + public static final int BAND_20 = android.hardware.radio.network.NgranBands.BAND_20; + public static final int BAND_25 = android.hardware.radio.network.NgranBands.BAND_25; + public static final int BAND_26 = android.hardware.radio.network.NgranBands.BAND_26; + public static final int BAND_28 = android.hardware.radio.network.NgranBands.BAND_28; + public static final int BAND_29 = android.hardware.radio.network.NgranBands.BAND_29; + public static final int BAND_30 = android.hardware.radio.network.NgranBands.BAND_30; + public static final int BAND_34 = android.hardware.radio.network.NgranBands.BAND_34; + public static final int BAND_38 = android.hardware.radio.network.NgranBands.BAND_38; + public static final int BAND_39 = android.hardware.radio.network.NgranBands.BAND_39; + public static final int BAND_40 = android.hardware.radio.network.NgranBands.BAND_40; + public static final int BAND_41 = android.hardware.radio.network.NgranBands.BAND_41; + public static final int BAND_46 = android.hardware.radio.network.NgranBands.BAND_46; + public static final int BAND_48 = android.hardware.radio.network.NgranBands.BAND_48; + public static final int BAND_50 = android.hardware.radio.network.NgranBands.BAND_50; + public static final int BAND_51 = android.hardware.radio.network.NgranBands.BAND_51; + public static final int BAND_53 = android.hardware.radio.network.NgranBands.BAND_53; + public static final int BAND_65 = android.hardware.radio.network.NgranBands.BAND_65; + public static final int BAND_66 = android.hardware.radio.network.NgranBands.BAND_66; + public static final int BAND_70 = android.hardware.radio.network.NgranBands.BAND_70; + public static final int BAND_71 = android.hardware.radio.network.NgranBands.BAND_71; + public static final int BAND_74 = android.hardware.radio.network.NgranBands.BAND_74; + public static final int BAND_75 = android.hardware.radio.network.NgranBands.BAND_75; + public static final int BAND_76 = android.hardware.radio.network.NgranBands.BAND_76; + public static final int BAND_77 = android.hardware.radio.network.NgranBands.BAND_77; + public static final int BAND_78 = android.hardware.radio.network.NgranBands.BAND_78; + public static final int BAND_79 = android.hardware.radio.network.NgranBands.BAND_79; + public static final int BAND_80 = android.hardware.radio.network.NgranBands.BAND_80; + public static final int BAND_81 = android.hardware.radio.network.NgranBands.BAND_81; + public static final int BAND_82 = android.hardware.radio.network.NgranBands.BAND_82; + public static final int BAND_83 = android.hardware.radio.network.NgranBands.BAND_83; + public static final int BAND_84 = android.hardware.radio.network.NgranBands.BAND_84; + public static final int BAND_86 = android.hardware.radio.network.NgranBands.BAND_86; + public static final int BAND_89 = android.hardware.radio.network.NgranBands.BAND_89; + public static final int BAND_90 = android.hardware.radio.network.NgranBands.BAND_90; + public static final int BAND_91 = android.hardware.radio.network.NgranBands.BAND_91; + public static final int BAND_92 = android.hardware.radio.network.NgranBands.BAND_92; + public static final int BAND_93 = android.hardware.radio.network.NgranBands.BAND_93; + public static final int BAND_94 = android.hardware.radio.network.NgranBands.BAND_94; + public static final int BAND_95 = android.hardware.radio.network.NgranBands.BAND_95; + public static final int BAND_96 = android.hardware.radio.network.NgranBands.BAND_96; /** 3GPP TS 38.101-2, Version 16.2.0, Table 5.2-1: FR2 bands */ - public static final int BAND_257 = android.hardware.radio.V1_5.NgranBands.BAND_257; - public static final int BAND_258 = android.hardware.radio.V1_5.NgranBands.BAND_258; - public static final int BAND_260 = android.hardware.radio.V1_5.NgranBands.BAND_260; - public static final int BAND_261 = android.hardware.radio.V1_5.NgranBands.BAND_261; + public static final int BAND_257 = android.hardware.radio.network.NgranBands.BAND_257; + public static final int BAND_258 = android.hardware.radio.network.NgranBands.BAND_258; + public static final int BAND_260 = android.hardware.radio.network.NgranBands.BAND_260; + public static final int BAND_261 = android.hardware.radio.network.NgranBands.BAND_261; /** * NR Bands diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index 29152f19d17d..971fc781a719 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -58,41 +58,41 @@ public final class BarringInfo implements Parcelable { BARRING_SERVICE_TYPE_SMS}) public @interface BarringServiceType {} - /* Applicabe to UTRAN */ + /* Applicable to UTRAN */ /** Barring indicator for circuit-switched service; applicable to UTRAN */ public static final int BARRING_SERVICE_TYPE_CS_SERVICE = - android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_SERVICE; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_SERVICE; /** Barring indicator for packet-switched service; applicable to UTRAN */ public static final int BARRING_SERVICE_TYPE_PS_SERVICE = - android.hardware.radio.V1_5.BarringInfo.ServiceType.PS_SERVICE; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_PS_SERVICE; /** Barring indicator for circuit-switched voice service; applicable to UTRAN */ public static final int BARRING_SERVICE_TYPE_CS_VOICE = - android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_VOICE; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_VOICE; /* Applicable to EUTRAN, NGRAN */ /** Barring indicator for mobile-originated signalling; applicable to EUTRAN and NGRAN */ public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING = - android.hardware.radio.V1_5.BarringInfo.ServiceType.MO_SIGNALLING; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MO_SIGNALLING; /** Barring indicator for mobile-originated data traffic; applicable to EUTRAN and NGRAN */ public static final int BARRING_SERVICE_TYPE_MO_DATA = - android.hardware.radio.V1_5.BarringInfo.ServiceType.MO_DATA; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MO_DATA; /** Barring indicator for circuit-switched fallback for voice; applicable to EUTRAN and NGRAN */ public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = - android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_FALLBACK; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_FALLBACK; /** Barring indicator for MMTEL (IMS) voice; applicable to EUTRAN and NGRAN */ public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE = - android.hardware.radio.V1_5.BarringInfo.ServiceType.MMTEL_VOICE; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MMTEL_VOICE; /** Barring indicator for MMTEL (IMS) video; applicable to EUTRAN and NGRAN */ public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO = - android.hardware.radio.V1_5.BarringInfo.ServiceType.MMTEL_VIDEO; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MMTEL_VIDEO; /* Applicable to UTRAN, EUTRAN, NGRAN */ /** Barring indicator for emergency services; applicable to UTRAN, EUTRAN, and NGRAN */ public static final int BARRING_SERVICE_TYPE_EMERGENCY = - android.hardware.radio.V1_5.BarringInfo.ServiceType.EMERGENCY; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_EMERGENCY; /** Barring indicator for SMS sending; applicable to UTRAN, EUTRAN, and NGRAN */ public static final int BARRING_SERVICE_TYPE_SMS = - android.hardware.radio.V1_5.BarringInfo.ServiceType.SMS; + android.hardware.radio.network.BarringInfo.SERVICE_TYPE_SMS; //TODO: add barring constants for Operator-Specific barring codes @@ -112,13 +112,13 @@ public final class BarringInfo implements Parcelable { /** Barring is inactive */ public static final int BARRING_TYPE_NONE = - android.hardware.radio.V1_5.BarringInfo.BarringType.NONE; + android.hardware.radio.network.BarringInfo.BARRING_TYPE_NONE; /** The service is barred */ public static final int BARRING_TYPE_UNCONDITIONAL = - android.hardware.radio.V1_5.BarringInfo.BarringType.UNCONDITIONAL; + android.hardware.radio.network.BarringInfo.BARRING_TYPE_UNCONDITIONAL; /** The service may be barred based on additional factors */ public static final int BARRING_TYPE_CONDITIONAL = - android.hardware.radio.V1_5.BarringInfo.BarringType.CONDITIONAL; + android.hardware.radio.network.BarringInfo.BARRING_TYPE_CONDITIONAL; /** If a modem does not report barring info, then the barring type will be UNKNOWN */ public static final int BARRING_TYPE_UNKNOWN = -1; diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index b0552b4a18a3..d8f63df9a71f 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -63,9 +63,9 @@ public final class NetworkRegistrationInfo implements Parcelable { /** Unknown / Unspecified domain */ public static final int DOMAIN_UNKNOWN = 0; /** Circuit switched domain */ - public static final int DOMAIN_CS = android.hardware.radio.V1_5.Domain.CS; + public static final int DOMAIN_CS = android.hardware.radio.network.Domain.CS; /** Packet switched domain */ - public static final int DOMAIN_PS = android.hardware.radio.V1_5.Domain.PS; + public static final int DOMAIN_PS = android.hardware.radio.network.Domain.PS; /** Applicable to both CS and PS Domain */ public static final int DOMAIN_CS_PS = DOMAIN_CS | DOMAIN_PS; diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 28ea5a681730..1b5c53749b9b 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -22,7 +22,7 @@ import android.annotation.StringDef; import android.annotation.SystemApi; import android.content.ContentValues; import android.database.Cursor; -import android.hardware.radio.V1_5.ApnTypes; +import android.hardware.radio.data.ApnTypes; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -116,12 +116,11 @@ public class ApnSetting implements Parcelable { /** APN type for XCAP. */ public static final int TYPE_XCAP = ApnTypes.XCAP; /** APN type for VSIM. */ - public static final int TYPE_VSIM = 1 << 12; // TODO: Refer to ApnTypes.VSIM + public static final int TYPE_VSIM = ApnTypes.VSIM; /** APN type for BIP. */ - public static final int TYPE_BIP = 1 << 13; // TODO: Refer to ApnTypes.BIP + public static final int TYPE_BIP = ApnTypes.BIP; /** APN type for ENTERPRISE. */ - public static final int TYPE_ENTERPRISE = 1 << 14; //TODO: In future should be referenced from - // hardware.interfaces.radio.data.ApnTypes + public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE; /** @hide */ @IntDef(flag = true, prefix = {"TYPE_"}, value = { diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java index a0d9c1bdf1ed..baa160e50cad 100644 --- a/telephony/java/android/telephony/data/QosBearerFilter.java +++ b/telephony/java/android/telephony/data/QosBearerFilter.java @@ -49,17 +49,13 @@ public final class QosBearerFilter implements Parcelable { public @interface QosProtocol {} public static final int QOS_PROTOCOL_UNSPECIFIED = - android.hardware.radio.V1_6.QosProtocol.UNSPECIFIED; - public static final int QOS_PROTOCOL_TCP = android.hardware.radio.V1_6.QosProtocol.TCP; - public static final int QOS_PROTOCOL_UDP = android.hardware.radio.V1_6.QosProtocol.UDP; - public static final int QOS_PROTOCOL_ESP = android.hardware.radio.V1_6.QosProtocol.ESP; - public static final int QOS_PROTOCOL_AH = android.hardware.radio.V1_6.QosProtocol.AH; - public static final int QOS_MIN_PORT = android.hardware.radio.V1_6.QosPortRange.MIN; - /** - * Hardcoded in place of android.hardware.radio.V1_6.QosPortRange.MAX as it - * returns -1 due to uint16_t to int conversion in java. (TODO: Fix the HAL) - */ - public static final int QOS_MAX_PORT = 65535; // android.hardware.radio.V1_6.QosPortRange.MIN; + android.hardware.radio.data.QosFilter.PROTOCOL_UNSPECIFIED; + public static final int QOS_PROTOCOL_TCP = android.hardware.radio.data.QosFilter.PROTOCOL_TCP; + public static final int QOS_PROTOCOL_UDP = android.hardware.radio.data.QosFilter.PROTOCOL_UDP; + public static final int QOS_PROTOCOL_ESP = android.hardware.radio.data.QosFilter.PROTOCOL_ESP; + public static final int QOS_PROTOCOL_AH = android.hardware.radio.data.QosFilter.PROTOCOL_AH; + public static final int QOS_MIN_PORT = android.hardware.radio.data.PortRange.PORT_RANGE_MIN; + public static final int QOS_MAX_PORT = android.hardware.radio.data.PortRange.PORT_RANGE_MAX; private @QosProtocol int protocol; @@ -78,11 +74,11 @@ public final class QosBearerFilter implements Parcelable { public @interface QosBearerFilterDirection {} public static final int QOS_FILTER_DIRECTION_DOWNLINK = - android.hardware.radio.V1_6.QosFilterDirection.DOWNLINK; + android.hardware.radio.data.QosFilter.DIRECTION_DOWNLINK; public static final int QOS_FILTER_DIRECTION_UPLINK = - android.hardware.radio.V1_6.QosFilterDirection.UPLINK; + android.hardware.radio.data.QosFilter.DIRECTION_UPLINK; public static final int QOS_FILTER_DIRECTION_BIDIRECTIONAL = - android.hardware.radio.V1_6.QosFilterDirection.BIDIRECTIONAL; + android.hardware.radio.data.QosFilter.DIRECTION_BIDIRECTIONAL; private @QosBearerFilterDirection int filterDirection; diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index 64bcf7171ade..b429407d8f31 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -19,8 +19,7 @@ package android.telephony.emergency; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; -import android.hardware.radio.V1_4.EmergencyNumberSource; -import android.hardware.radio.V1_4.EmergencyServiceCategory; +import android.hardware.radio.voice.EmergencyServiceCategory; import android.os.Parcel; import android.os.Parcelable; import android.telephony.CarrierConfigManager; @@ -172,13 +171,14 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu * Reference: 3gpp 22.101, Section 10 - Emergency Calls */ public static final int EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING = - EmergencyNumberSource.NETWORK_SIGNALING; + android.hardware.radio.voice.EmergencyNumber.SOURCE_NETWORK_SIGNALING; /** * Bit-field which indicates the number is from the sim. * * Reference: 3gpp 22.101, Section 10 - Emergency Calls */ - public static final int EMERGENCY_NUMBER_SOURCE_SIM = EmergencyNumberSource.SIM; + public static final int EMERGENCY_NUMBER_SOURCE_SIM = + android.hardware.radio.voice.EmergencyNumber.SOURCE_SIM; /** * Bit-field which indicates the number is from the platform-maintained database. */ @@ -192,7 +192,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu public static final int EMERGENCY_NUMBER_SOURCE_TEST = 1 << 5; /** Bit-field which indicates the number is from the modem config. */ public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = - EmergencyNumberSource.MODEM_CONFIG; + android.hardware.radio.voice.EmergencyNumber.SOURCE_MODEM_CONFIG; /** * Bit-field which indicates the number is available as default. * @@ -201,7 +201,8 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu * * Reference: 3gpp 22.101, Section 10 - Emergency Calls */ - public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT = EmergencyNumberSource.DEFAULT; + public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT = + android.hardware.radio.voice.EmergencyNumber.SOURCE_DEFAULT; private static final Set<Integer> EMERGENCY_NUMBER_SOURCE_SET; static { diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 4ba538ed9d45..2f96eecb27e3 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -23,14 +23,49 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "FlickerTests", +filegroup { + name: "FlickerTestsBase-src", + srcs: ["src/com/android/server/wm/flicker/*.kt"], +} + +filegroup { + name: "FlickerTestsAppClose-src", + srcs: ["src/**/close/*.kt"], +} + +filegroup { + name: "FlickerTestsActivityEmbedding-src", srcs: [ - "src/**/*.java", - "src/**/*.kt", + "src/**/activityembedding/*.kt", + "src/**/activityembedding/close/*.kt", + "src/**/activityembedding/rotation/*.kt", ], - manifest: "AndroidManifest.xml", - test_config: "AndroidTest.xml", +} + +filegroup { + name: "FlickerTestsIme-src", + srcs: ["src/**/ime/*.kt"], +} + +filegroup { + name: "FlickerTestsAppLaunch-src", + srcs: ["src/**/launch/*.kt"], +} + +filegroup { + name: "FlickerTestsQuickswitch-src", + srcs: ["src/**/quickswitch/*.kt"], +} + +filegroup { + name: "FlickerTestsRotation-src", + srcs: ["src/**/rotation/*.kt"], +} + +java_defaults { + name: "FlickerTestsDefault", + manifest: "manifests/AndroidManifest.xml", + test_config_template: "AndroidTestTemplate.xml", platform_apis: true, certificate: "platform", optimize: { @@ -42,19 +77,100 @@ android_test { "androidx.test.ext.junit", "flickertestapplib", "flickerlib", - "flickerlib-apphelpers", "flickerlib-helpers", - "truth-prebuilt", - "launcher-helper-lib", - "launcher-aosp-tapl", "platform-test-annotations", - "wm-flicker-window-extensions", + "wm-flicker-common-app-helpers", ], data: [ ":FlickerTestApp", ], } +android_test { + name: "FlickerTestsOther", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestOther.xml"], + package_name: "com.android.server.wm.flicker", + instrumentation_target_package: "com.android.server.wm.flicker", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + exclude_srcs: [ + ":FlickerTestsAppClose-src", + ":FlickerTestsIme-src", + ":FlickerTestsAppLaunch-src", + ":FlickerTestsQuickswitch-src", + ":FlickerTestsRotation-src", + ], +} + +android_test { + name: "FlickerTestsAppClose", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestAppClose.xml"], + package_name: "com.android.server.wm.flicker.close", + instrumentation_target_package: "com.android.server.wm.flicker.close", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerTestsAppClose-src", + ], + exclude_srcs: [ + ":FlickerTestsActivityEmbedding-src", + ], +} + +android_test { + name: "FlickerTestsIme", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestIme.xml"], + package_name: "com.android.server.wm.flicker.ime", + instrumentation_target_package: "com.android.server.wm.flicker.ime", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerTestsIme-src", + ], +} + +android_test { + name: "FlickerTestsAppLaunch", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"], + package_name: "com.android.server.wm.flicker.launch", + instrumentation_target_package: "com.android.server.wm.flicker.launch", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerTestsAppLaunch-src", + ], +} + +android_test { + name: "FlickerTestsQuickswitch", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestQuickswitch.xml"], + package_name: "com.android.server.wm.flicker.rotation", + instrumentation_target_package: "com.android.server.wm.flicker.rotation", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerTestsQuickswitch-src", + ], +} + +android_test { + name: "FlickerTestsRotation", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestRotation.xml"], + package_name: "com.android.server.wm.flicker.rotation", + instrumentation_target_package: "com.android.server.wm.flicker.rotation", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerTestsRotation-src", + ], + exclude_srcs: [ + ":FlickerTestsActivityEmbedding-src", + ], +} + java_library { name: "wm-flicker-common-assertions", platform_apis: true, diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 32ff243921ec..1176828d47c3 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -2,7 +2,7 @@ <!-- * Copyright 2018 Google Inc. All Rights Reserved. --> -<configuration description="Runs WindowManager Flicker Tests"> +<configuration description="Runs WindowManager {MODULE}"> <option name="test-tag" value="FlickerTests" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> @@ -36,11 +36,11 @@ </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> - <option name="test-file-name" value="FlickerTests.apk"/> + <option name="test-file-name" value="{MODULE}.apk"/> <option name="test-file-name" value="FlickerTestApp.apk" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.server.wm.flicker"/> + <option name="package" value="{PACKAGE}"/> <option name="shell-timeout" value="6600s" /> <option name="test-timeout" value="6600s" /> <option name="hidden-api-checks" value="false" /> diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml index 462f91bc081c..5b00310cd874 100644 --- a/tests/FlickerTests/AndroidManifest.xml +++ b/tests/FlickerTests/manifests/AndroidManifest.xml @@ -1,18 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 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. ---> +<!-- + ~ 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. + --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.wm.flicker"> @@ -48,8 +49,8 @@ <uses-library android:name="androidx.window.extensions" android:required="false"/> </application> - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.server.wm.flicker" - android:label="WindowManager Flicker Tests"> - </instrumentation> + <!--<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="{{package}}" + android:label="WindowManager Flicker Tests {MODULE}"> + </instrumentation>--> </manifest> diff --git a/tests/FlickerTests/manifests/AndroidManifestAppClose.xml b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml new file mode 100644 index 000000000000..4cdcb903b498 --- /dev/null +++ b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker.close"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker.close" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml new file mode 100644 index 000000000000..659a745ba480 --- /dev/null +++ b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker.launch"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker.launch" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/tests/FlickerTests/manifests/AndroidManifestIme.xml b/tests/FlickerTests/manifests/AndroidManifestIme.xml new file mode 100644 index 000000000000..abd03af4888a --- /dev/null +++ b/tests/FlickerTests/manifests/AndroidManifestIme.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker.ime"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker.ime" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/tests/FlickerTests/manifests/AndroidManifestOther.xml b/tests/FlickerTests/manifests/AndroidManifestOther.xml new file mode 100644 index 000000000000..47749b8133b1 --- /dev/null +++ b/tests/FlickerTests/manifests/AndroidManifestOther.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml new file mode 100644 index 000000000000..203035d30584 --- /dev/null +++ b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker.quickswitch"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker.quickswitch" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/tests/FlickerTests/manifests/AndroidManifestRotation.xml b/tests/FlickerTests/manifests/AndroidManifestRotation.xml new file mode 100644 index 000000000000..2852cf23a35b --- /dev/null +++ b/tests/FlickerTests/manifests/AndroidManifestRotation.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker.rotation"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker.rotation" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> |