summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/StubLibraries.bp44
-rw-r--r--core/api/current.txt4
-rw-r--r--core/api/module-lib-current.txt2
-rw-r--r--core/api/test-current.txt3
-rw-r--r--core/java/android/credentials/CredentialProviderInfo.java37
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java18
-rw-r--r--core/java/android/hardware/biometrics/IAuthService.aidl4
-rw-r--r--core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl27
-rw-r--r--core/java/android/hardware/biometrics/IBiometricService.aidl5
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfoFactory.java32
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java12
-rw-r--r--core/java/android/view/Window.java43
-rw-r--r--core/java/android/view/WindowManager.java47
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml27
-rw-r--r--core/res/res/values/attrs.xml3
-rw-r--r--core/res/res/values/strings.xml13
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java60
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt79
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java71
-rw-r--r--media/java/android/media/AudioManager.java9
-rw-r--r--media/java/android/media/projection/OWNERS1
-rw-r--r--media/java/android/media/tv/TvInputInfo.java4
-rw-r--r--packages/CompanionDeviceManager/res/values/styles.xml2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java8
-rw-r--r--packages/SystemUI/proguard_common.flags46
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/Observer.java3
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java95
-rw-r--r--services/core/java/com/android/server/am/AnrTimer.java129
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java15
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java49
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java13
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java72
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java43
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java19
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java9
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java132
-rw-r--r--services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java205
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java35
-rw-r--r--services/tests/displayservicetests/Android.bp1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java17
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java5
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java130
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java148
-rw-r--r--services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java41
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt8
67 files changed, 1769 insertions, 196 deletions
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 2d9c988556ec..fa4bc0f98fa5 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -438,6 +438,26 @@ java_api_library {
full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
}
+// This module generates a stub jar that is a union of the test and module lib
+// non-updatable api contributions. Modules should not depend on the stub jar
+// generated from this module, as this module is strictly used for hiddenapi only.
+java_api_library {
+ name: "android-non-updatable.stubs.test_module_lib",
+ api_surface: "module_lib",
+ api_contributions: [
+ "api-stubs-docs-non-updatable.api.contribution",
+ "system-api-stubs-docs-non-updatable.api.contribution",
+ "test-api-stubs-docs-non-updatable.api.contribution",
+ "module-lib-api-stubs-docs-non-updatable.api.contribution",
+ ],
+ defaults: ["android-non-updatable_from_text_defaults"],
+ full_api_surface_stub: "android_test_module_lib_stubs_current.from-text",
+
+ // This module is only used for hiddenapi, and other modules should not
+ // depend on this module.
+ visibility: ["//visibility:private"],
+}
+
java_defaults {
name: "android_stubs_dists_default",
dist: {
@@ -757,6 +777,30 @@ java_api_library {
}
java_api_library {
+ name: "android_test_module_lib_stubs_current.from-text",
+ api_surface: "module-lib",
+ defaults: [
+ "android_stubs_current_contributions",
+ "android_system_stubs_current_contributions",
+ "android_test_stubs_current_contributions",
+ "android_module_lib_stubs_current_contributions",
+ ],
+ libs: [
+ "android_module_lib_stubs_current_full.from-text",
+ "stub-annotations",
+ ],
+ api_contributions: [
+ "test-api-stubs-docs-non-updatable.api.contribution",
+ ],
+
+ // This module is only used to build android-non-updatable.stubs.test_module_lib
+ // and other modules should not depend on this module.
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+java_api_library {
name: "android_system_server_stubs_current.from-text",
api_surface: "system-server",
api_contributions: [
diff --git a/core/api/current.txt b/core/api/current.txt
index f908d9546a34..20424beb2447 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -53129,6 +53129,7 @@ package android.view {
method @Nullable public abstract android.view.View getCurrentFocus();
method @NonNull public abstract android.view.View getDecorView();
method public static int getDefaultFeatures(android.content.Context);
+ method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
method public android.transition.Transition getEnterTransition();
method public android.transition.Transition getExitTransition();
method protected final int getFeatures();
@@ -53198,6 +53199,7 @@ package android.view {
method public abstract void setDecorCaptionShade(int);
method public void setDecorFitsSystemWindows(boolean);
method protected void setDefaultWindowFormat(int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
method public void setDimAmount(float);
method public void setElevation(float);
method public void setEnterTransition(android.transition.Transition);
@@ -53547,6 +53549,7 @@ package android.view {
method public int describeContents();
method public int getBlurBehindRadius();
method public int getColorMode();
+ method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
method public int getFitInsetsSides();
method public int getFitInsetsTypes();
method public final CharSequence getTitle();
@@ -53556,6 +53559,7 @@ package android.view {
method public void setBlurBehindRadius(@IntRange(from=0) int);
method public void setCanPlayMoveAnimation(boolean);
method public void setColorMode(int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0f) float);
method public void setFitInsetsIgnoringVisibility(boolean);
method public void setFitInsetsSides(int);
method public void setFitInsetsTypes(int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 81579a214bc9..500a12cacc3b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -8,9 +8,7 @@ package android {
field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
- field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
- field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
}
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 804c19693066..eeddeb21aa9d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -30,7 +30,6 @@ package android {
field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
- field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE";
@@ -58,7 +57,6 @@ package android {
field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
- field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
@@ -1183,6 +1181,7 @@ package android.credentials {
method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
+ method @FlaggedApi("android.credentials.flags.settings_activity_enabled") @Nullable public CharSequence getSettingsActivity();
method @Nullable public CharSequence getSettingsSubtitle();
method @NonNull public boolean hasCapability(@NonNull String);
method public boolean isEnabled();
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 95ca0112b8b3..8503072838d1 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -16,12 +16,14 @@
package android.credentials;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo;
+import android.credentials.flags.Flags;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -42,6 +44,7 @@ public final class CredentialProviderInfo implements Parcelable {
@NonNull private final List<String> mCapabilities = new ArrayList<>();
@Nullable private final CharSequence mOverrideLabel;
@Nullable private CharSequence mSettingsSubtitle = null;
+ @Nullable private CharSequence mSettingsActivity = null;
private final boolean mIsSystemProvider;
private final boolean mIsEnabled;
private final boolean mIsPrimary;
@@ -59,6 +62,7 @@ public final class CredentialProviderInfo implements Parcelable {
mIsEnabled = builder.mIsEnabled;
mIsPrimary = builder.mIsPrimary;
mOverrideLabel = builder.mOverrideLabel;
+ mSettingsActivity = builder.mSettingsActivity;
}
/** Returns true if the service supports the given {@code credentialType}, false otherwise. */
@@ -104,10 +108,7 @@ public final class CredentialProviderInfo implements Parcelable {
return mIsEnabled;
}
- /**
- * Returns whether the provider is set as primary by the user.
- *
- */
+ /** Returns whether the provider is set as primary by the user. */
public boolean isPrimary() {
return mIsPrimary;
}
@@ -118,6 +119,18 @@ public final class CredentialProviderInfo implements Parcelable {
return mSettingsSubtitle;
}
+ /**
+ * Returns the settings activity.
+ *
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)
+ public CharSequence getSettingsActivity() {
+ return mSettingsActivity;
+ }
+
/** Returns the component name for the service. */
@NonNull
public ComponentName getComponentName() {
@@ -133,6 +146,7 @@ public final class CredentialProviderInfo implements Parcelable {
dest.writeBoolean(mIsPrimary);
TextUtils.writeToParcel(mOverrideLabel, dest, flags);
TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
+ TextUtils.writeToParcel(mSettingsActivity, dest, flags);
}
@Override
@@ -161,6 +175,9 @@ public final class CredentialProviderInfo implements Parcelable {
+ "settingsSubtitle="
+ mSettingsSubtitle
+ ", "
+ + "settingsActivity="
+ + mSettingsActivity
+ + ", "
+ "capabilities="
+ String.join(",", mCapabilities)
+ "}";
@@ -174,6 +191,7 @@ public final class CredentialProviderInfo implements Parcelable {
mIsPrimary = in.readBoolean();
mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSettingsActivity = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
}
public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
@@ -196,6 +214,7 @@ public final class CredentialProviderInfo implements Parcelable {
@NonNull private List<String> mCapabilities = new ArrayList<>();
private boolean mIsSystemProvider = false;
@Nullable private CharSequence mSettingsSubtitle = null;
+ @Nullable private CharSequence mSettingsActivity = null;
private boolean mIsEnabled = false;
private boolean mIsPrimary = false;
@Nullable private CharSequence mOverrideLabel = null;
@@ -231,6 +250,16 @@ public final class CredentialProviderInfo implements Parcelable {
return this;
}
+ /**
+ * Sets the settings activity.
+ *
+ * @hide
+ */
+ public @NonNull Builder setSettingsActivity(@Nullable CharSequence settingsActivity) {
+ mSettingsActivity = settingsActivity;
+ return this;
+ }
+
/** Sets a list of capabilities this provider service can support. */
public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
mCapabilities.addAll(capabilities);
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 82694ee3463b..9c05dfc94ad4 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -537,6 +537,24 @@ public class BiometricManager {
}
/**
+ * Listens for biometric prompt status, i.e., if it is being shown or idle.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void registerBiometricPromptStatusListener(
+ IBiometricPromptStatusListener callback) {
+ if (mService != null) {
+ try {
+ mService.registerBiometricPromptStatusListener(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Slog.w(TAG, "registerBiometricPromptOnKeyguardCallback(): Service not connected");
+ }
+ }
+
+ /**
* Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their
* authenticatorId invalidated for the specified user. This happens when enrollments have been
* added on devices with multiple biometric sensors.
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index c2e5c0b6d519..8eede472bec5 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -17,6 +17,7 @@
package android.hardware.biometrics;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
@@ -63,6 +64,9 @@ interface IAuthService {
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
+ // Register callback to check biometric prompt status.
+ void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback);
+
// Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
// specified user. This happens when enrollments have been added on devices with multiple
// biometric sensors.
diff --git a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
new file mode 100644
index 000000000000..7a0f4389ed60
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+/**
+ * Communication channel to propagate biometric prompt status. Implementation of this interface
+ * should be registered in BiometricService#registerBiometricPromptStatusListener.
+ * @hide
+ */
+oneway interface IBiometricPromptStatusListener {
+ void onBiometricPromptShowing();
+ void onBiometricPromptIdle();
+} \ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 18c8d1bd3a1e..36606a135f3e 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -17,6 +17,7 @@
package android.hardware.biometrics;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IInvalidationCallback;
@@ -68,6 +69,10 @@ interface IBiometricService {
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
+ // Register a callback for biometric prompt status on keyguard.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback);
+
// Notify BiometricService when <Biometric>Service is ready to start the prepared client.
// Client lifecycle is still managed in <Biometric>Service.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 0aa6a232d416..514d72252c3d 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -45,8 +45,8 @@ import android.util.AttributeSet;
import android.util.Slog;
import android.util.Xml;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -135,8 +135,8 @@ public final class CredentialProviderInfoFactory {
}
/**
- * Constructs an information instance of the credential provider for testing purposes. Does
- * not run any verifications and passes parameters as is.
+ * Constructs an information instance of the credential provider for testing purposes. Does not
+ * run any verifications and passes parameters as is.
*/
@VisibleForTesting
public static CredentialProviderInfo createForTests(
@@ -151,7 +151,6 @@ public final class CredentialProviderInfoFactory {
.setSystemProvider(isSystemProvider)
.addCapabilities(capabilities)
.build();
-
}
private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -267,15 +266,21 @@ public final class CredentialProviderInfoFactory {
allAttributes,
com.android.internal.R.styleable.CredentialProvider);
builder.setSettingsSubtitle(
- afsAttributes.getString(
+ getAfsAttributeSafe(
+ afsAttributes,
R.styleable.CredentialProvider_settingsSubtitle));
+ builder.setSettingsActivity(
+ getAfsAttributeSafe(
+ afsAttributes,
+ R.styleable.CredentialProvider_settingsActivity));
} catch (Exception e) {
- Slog.e(TAG, "Failed to get XML attr", e);
+ Slog.w(TAG, "Failed to get XML attr for metadata", e);
} finally {
if (afsAttributes != null) {
afsAttributes.recycle();
}
}
+
builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources));
} else {
Slog.w(TAG, "Meta-data does not start with credential-provider-service tag");
@@ -287,6 +292,21 @@ public final class CredentialProviderInfoFactory {
return builder;
}
+ private static @Nullable String getAfsAttributeSafe(
+ @Nullable TypedArray afsAttributes, int resId) {
+ if (afsAttributes == null) {
+ return null;
+ }
+
+ try {
+ return afsAttributes.getString(resId);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to get XML attr from afs attributes", e);
+ }
+
+ return null;
+ }
+
private static List<String> parseXmlProviderOuterCapabilities(
XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
final List<String> capabilities = new ArrayList<>();
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 79c8fb4a6620..c82a4cabaddd 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -26,6 +26,7 @@ import android.system.ErrnoException;
import android.system.OsConstants;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
@@ -138,6 +139,7 @@ public class NotificationRankingUpdate implements Parcelable {
*
* @hide
*/
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
public final boolean isFdNotNullAndClosed() {
return mRankingMapFd != null && mRankingMapFd.getFd() == -1;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e111dc8088de..cf8a5b42a8b9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1730,7 +1730,7 @@ public final class ViewRootImpl implements ViewParent,
attrs.getTitle().toString());
mAttachInfo.mThreadedRenderer = renderer;
renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
- updateColorModeIfNeeded(attrs.getColorMode());
+ updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom());
updateRenderHdrSdrRatio();
updateForceDarkMode();
mAttachInfo.mHardwareAccelerated = true;
@@ -3349,7 +3349,7 @@ public final class ViewRootImpl implements ViewParent,
}
final boolean alwaysConsumeSystemBarsChanged =
mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
- updateColorModeIfNeeded(lp.getColorMode());
+ updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom());
surfaceCreated = !hadSurface && mSurface.isValid();
surfaceDestroyed = hadSurface && !mSurface.isValid();
@@ -5652,7 +5652,8 @@ public final class ViewRootImpl implements ViewParent,
mUpdateHdrSdrRatioInfo = true;
}
- private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) {
+ private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode,
+ float desiredRatio) {
if (mAttachInfo.mThreadedRenderer == null) {
return;
}
@@ -5666,7 +5667,10 @@ public final class ViewRootImpl implements ViewParent,
&& !getConfiguration().isScreenWideColorGamut()) {
colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
}
- float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
+ float automaticRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
+ if (desiredRatio == 0 || desiredRatio > automaticRatio) {
+ desiredRatio = automaticRatio;
+ }
if (desiredRatio != mDesiredHdrSdrRatio) {
mDesiredHdrSdrRatio = desiredRatio;
updateRenderHdrSdrRatio();
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 2f04b0c695da..87537fbc9961 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -24,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
@@ -1330,6 +1332,47 @@ public abstract class Window {
}
/**
+ * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+ * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
+ * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
+ *
+ * <p>By default the system will choose an amount of HDR headroom that is appropriate
+ * for the underlying device capabilities & bit-depth of the panel. However, for some types
+ * of content this can end up being more headroom than necessary or desired. An example
+ * would be a messaging app or gallery thumbnail view where some amount of HDR pop is desired
+ * without overly influencing the perceived brightness of the majority SDR content. This can
+ * also be used to animate in/out of an HDR range for smoother transitions.</p>
+ *
+ * <p>Note: The actual amount of HDR headroom that will be given is subject to a variety
+ * of factors such as ambient conditions, display capabilities, or bit-depth limitations.
+ * See {@link Display#getHdrSdrRatio()} for more information as well as how to query the
+ * current value.</p>
+ *
+ * @param desiredHeadroom The amount of HDR headroom that is desired. Must be >= 1.0 (no HDR)
+ * and <= 10,000.0. Passing 0.0 will reset to the default, automatically
+ * chosen value.
+ * @see #getDesiredHdrHeadroom()
+ * @see Display#getHdrSdrRatio()
+ */
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+ public void setDesiredHdrHeadroom(
+ @FloatRange(from = 0.0f, to = 10000.0) float desiredHeadroom) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setDesiredHdrHeadroom(desiredHeadroom);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)}
+ * @return The amount of HDR headroom set, or 0 for automatic/default behavior.
+ * @see #setDesiredHdrHeadroom(float)
+ */
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+ public float getDesiredHdrHeadroom() {
+ return getAttributes().getDesiredHdrHeadroom();
+ }
+
+ /**
* If {@code isPreferred} is true, this method requests that the connected display does minimal
* post processing when this window is visible on the screen. Otherwise, it requests that the
* display switches back to standard image processing.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 08f9c8cdd2a8..6c8f5424f85e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -83,6 +83,7 @@ import static android.view.WindowLayoutParamsProto.Y;
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -4315,6 +4316,9 @@ public interface WindowManager extends ViewManager {
@ActivityInfo.ColorMode
private int mColorMode = COLOR_MODE_DEFAULT;
+ /** @hide */
+ private float mDesiredHdrHeadroom = 0;
+
/**
* Carries the requests about {@link WindowInsetsController.Appearance} and
* {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
@@ -4717,6 +4721,39 @@ public interface WindowManager extends ViewManager {
}
/**
+ * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+ * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
+ * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
+ *
+ * @see Window#setDesiredHdrHeadroom(float)
+ * @param desiredHeadroom Desired amount of HDR headroom. Must be in the range of 1.0 (SDR)
+ * to 10,000.0, or 0.0 to reset to default.
+ */
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+ public void setDesiredHdrHeadroom(
+ @FloatRange(from = 0.0f, to = 10000.0f) float desiredHeadroom) {
+ if (!Float.isFinite(desiredHeadroom)) {
+ throw new IllegalArgumentException("desiredHeadroom must be finite: "
+ + desiredHeadroom);
+ }
+ if (desiredHeadroom != 0 && (desiredHeadroom < 1.0f || desiredHeadroom > 10000.0f)) {
+ throw new IllegalArgumentException(
+ "desiredHeadroom must be 0.0 or in the range [1.0, 10000.0f], received: "
+ + desiredHeadroom);
+ }
+ mDesiredHdrHeadroom = desiredHeadroom;
+ }
+
+ /**
+ * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)}
+ * @return The amount of HDR headroom set, or 0 for automatic/default behavior.
+ */
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+ public float getDesiredHdrHeadroom() {
+ return mDesiredHdrHeadroom;
+ }
+
+ /**
* <p>
* Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount},
* but instead of dimmed, the content behind the window will be blurred (or combined with
@@ -4866,6 +4903,7 @@ public interface WindowManager extends ViewManager {
checkNonRecursiveParams();
out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
out.writeInt(mDisplayFlags);
+ out.writeFloat(mDesiredHdrHeadroom);
}
public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -4937,6 +4975,7 @@ public interface WindowManager extends ViewManager {
forciblyShownTypes = in.readInt();
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
mDisplayFlags = in.readInt();
+ mDesiredHdrHeadroom = in.readFloat();
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -5197,6 +5236,11 @@ public interface WindowManager extends ViewManager {
changes |= COLOR_MODE_CHANGED;
}
+ if (mDesiredHdrHeadroom != o.mDesiredHdrHeadroom) {
+ mDesiredHdrHeadroom = o.mDesiredHdrHeadroom;
+ changes |= COLOR_MODE_CHANGED;
+ }
+
if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) {
preferMinimalPostProcessing = o.preferMinimalPostProcessing;
changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED;
@@ -5424,6 +5468,9 @@ public interface WindowManager extends ViewManager {
if (mColorMode != COLOR_MODE_DEFAULT) {
sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
}
+ if (mDesiredHdrHeadroom != 0) {
+ sb.append(" desiredHdrHeadroom=").append(mDesiredHdrHeadroom);
+ }
if (preferMinimalPostProcessing) {
sb.append(" preferMinimalPostProcessing=");
sb.append(preferMinimalPostProcessing);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b0ecc60de597..ca768ad434f1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2108,12 +2108,12 @@
android:protectionLevel="signature" />
<!-- Allows direct access to the <RemoteAuth>Service interfaces.
- @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ @hide -->
<permission android:name="android.permission.MANAGE_REMOTE_AUTH"
android:protectionLevel="signature" />
<!-- Allows direct access to the <RemoteAuth>Service authentication methods.
- @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ @hide -->
<permission android:name="android.permission.USE_REMOTE_AUTH"
android:protectionLevel="signature" />
diff --git a/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml
new file mode 100644
index 000000000000..dddad814b451
--- /dev/null
+++ b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml
@@ -0,0 +1,27 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M15.333,5.333V4.667C15.333,4.3 15.033,4 14.667,4L13.333,4C12.967,4 12.667,4.3 12.667,4.667V5.333H12V8C12,8.367 12.3,8.667 12.667,8.667H13.333L13.333,13.333C13.333,14.067 12.733,14.667 12,14.667C11.267,14.667 10.667,14.067 10.667,13.333L10.667,11.333V6.667C10.667,5.193 9.473,4 8,4C6.527,4 5.333,5.193 5.333,6.667L5.333,11.333H4.667C4.3,11.333 4,11.633 4,12L4,14.667H4.667V15.333C4.667,15.7 4.967,16 5.333,16H6.667C7.033,16 7.333,15.7 7.333,15.333V14.667H8L8,12C8,11.633 7.7,11.333 7.333,11.333H6.667L6.667,6.667C6.667,5.933 7.267,5.333 8,5.333C8.733,5.333 9.333,5.933 9.333,6.667V11.333L9.333,13.333C9.333,14.807 10.527,16 12,16C13.473,16 14.667,14.807 14.667,13.333L14.667,8.667H15.333C15.7,8.667 16,8.367 16,8V5.333H15.333Z"
+ android:fillColor="#FFFFFFFF"
+ android:fillType="evenOdd"/>
+</vector>
+
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e54347fc2744..04fd70a96201 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10118,6 +10118,9 @@
screen that can be used to provide more information about a provider. For
longer strings it will be truncated. -->
<attr name="settingsSubtitle" format="string" />
+ <!-- Fully qualified class name of an activity that allows the user to modify
+ the settings for this service. -->
+ <attr name="settingsActivity" />
</declare-styleable>
<!-- A list of capabilities that indicates to the OS what kinds of credentials
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fac6aac1db16..a2a4e34f3527 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -826,6 +826,9 @@
security policy. [CHAR_LIMIT=NONE]-->
<string name="notification_channel_accessibility_security_policy">Accessibility usage</string>
+ <!-- Text shown when viewing channel settings for notifications related to displays -->
+ <string name="notification_channel_display">Display</string>
+
<!-- Label for foreground service notification when one app is running.
[CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] -->
<string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is
@@ -6290,6 +6293,16 @@ ul.</string>
<!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
<string name="mic_access_off_toast">Microphone is blocked</string>
+ <!-- Title of connected display unavailable notifications. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_unavailable_notification_title">Can\'t mirror to display</string>
+ <!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_unavailable_notification_content">Use a different cable and try again</string>
+
+ <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string>
+ <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string>
+
<!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_name">Dual screen</string>
<!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 49a5a7224850..c1ecb05a6df2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1997,6 +1997,7 @@
<java-symbol type="drawable" name="stat_sys_throttled" />
<java-symbol type="drawable" name="vpn_connected" />
<java-symbol type="drawable" name="vpn_disconnected" />
+ <java-symbol type="drawable" name="usb_cable_unknown_issue" />
<java-symbol type="id" name="ask_checkbox" />
<java-symbol type="id" name="compat_checkbox" />
<java-symbol type="id" name="original_app_icon" />
@@ -3813,6 +3814,7 @@
<java-symbol type="string" name="notification_channel_do_not_disturb" />
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
+ <java-symbol type="string" name="notification_channel_display" />
<java-symbol type="string" name="config_defaultAutofillService" />
<java-symbol type="string" name="config_defaultFieldClassificationService" />
<java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
@@ -5066,6 +5068,10 @@
<java-symbol type="array" name="device_state_notification_thermal_contents"/>
<java-symbol type="array" name="device_state_notification_power_save_titles"/>
<java-symbol type="array" name="device_state_notification_power_save_contents"/>
+ <java-symbol type="string" name="connected_display_unavailable_notification_title"/>
+ <java-symbol type="string" name="connected_display_unavailable_notification_content"/>
+ <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/>
+ <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/>
<java-symbol type="string" name="concurrent_display_notification_name"/>
<java-symbol type="string" name="concurrent_display_notification_active_title"/>
<java-symbol type="string" name="concurrent_display_notification_active_content"/>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 5dfba5e7ff1d..14a040a40874 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -203,6 +203,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
@@ -218,6 +219,7 @@ public abstract class WMShellModule {
taskOrganizer,
displayController,
shellController,
+ displayInsetsController,
syncQueue,
transitions,
desktopTasksController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 226fe08a2f19..451e61855943 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -345,6 +345,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
// Keyguard handler cannot handle it, process through original mixed
mActiveTransitions.remove(keyguardMixed);
}
+ } else if (mPipHandler != null) {
+ mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 82fc0f49c143..aff35a347183 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -231,4 +231,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
int getCaptionHeightId(@WindowingMode int windowingMode) {
return R.dimen.freeform_decor_caption_height;
}
+
+ @Override
+ int getCaptionViewId() {
+ return R.id.caption;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index bf99ab35cdd7..ca91d580b4ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowInsets.Type.statusBars;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -50,6 +51,8 @@ import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -67,6 +70,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -131,6 +135,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
new DesktopModeKeyguardChangeListener();
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private boolean mInImmersiveMode;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -141,6 +147,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
@@ -156,6 +163,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
taskOrganizer,
displayController,
shellController,
+ displayInsetsController,
syncQueue,
transitions,
desktopTasksController,
@@ -176,6 +184,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
@@ -191,6 +200,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTaskOrganizer = taskOrganizer;
mShellController = shellController;
mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopTasksController = desktopTasksController;
@@ -213,6 +223,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
});
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+ new DesktopModeOnInsetsChangedListener());
}
@Override
@@ -655,9 +667,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
if (DesktopModeStatus.isEnabled()) {
- if (relevantDecor == null
+ if (!mInImmersiveMode && (relevantDecor == null
|| relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
- || mTransitionDragActive) {
+ || mTransitionDragActive)) {
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
@@ -1051,6 +1063,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return mIsKeyguardVisible && mIsKeyguardOccluded;
}
}
+
+ @VisibleForTesting
+ class DesktopModeOnInsetsChangedListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ for (int i = 0; i < insetsState.sourceSize(); i++) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() != statusBars()) {
+ continue;
+ }
+
+ final DesktopModeWindowDecoration decor = getFocusedDecor();
+ if (decor == null) {
+ return;
+ }
+ // If status bar inset is visible, top task is not in immersive mode
+ final boolean inImmersiveMode = !source.isVisible();
+ // Calls WindowDecoration#relayout if decoration visibility needs to be updated
+ if (inImmersiveMode != mInImmersiveMode) {
+ decor.relayout(decor.mTaskInfo);
+ mInImmersiveMode = inImmersiveMode;
+ }
+
+ return;
+ }
+ }
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 380b59e84485..248e83747c48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -638,6 +638,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
}
+ @Override
+ int getCaptionViewId() {
+ return R.id.desktop_mode_caption;
+ }
+
/**
* Add transition to mTransitionsPausingRelayout
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 335a5886ba28..0548a8e751cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.statusBars;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
@@ -30,6 +31,8 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.view.Display;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -119,6 +122,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private Configuration mWindowDecorConfig;
+ private boolean mIsCaptionVisible;
private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
@@ -225,6 +229,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.inflate(params.mLayoutResId, null);
}
+ updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+
final Resources resources = mDecorWindowContext.getResources();
final Configuration taskConfig = mTaskInfo.getConfiguration();
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
@@ -272,12 +278,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
- wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
- wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
- mCaptionInsetsRect);
+ if (mIsCaptionVisible) {
+ mCaptionInsetsRect.bottom =
+ mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+ mCaptionInsetsRect);
+ } else {
+ wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+ WindowInsets.Type.captionBar());
+ wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+ WindowInsets.Type.mandatorySystemGestures());
+ }
} else {
startT.hide(mCaptionContainerSurface);
}
@@ -348,10 +362,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ /**
+ * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
+ */
+ private void updateCaptionVisibility(View rootView, int displayId) {
+ final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
+ for (int i = 0; i < insetsState.sourceSize(); i++) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() != statusBars()) {
+ continue;
+ }
+
+ mIsCaptionVisible = source.isVisible();
+ setCaptionVisibility(rootView, mIsCaptionVisible);
+
+ return;
+ }
+ }
+
+ private void setCaptionVisibility(View rootView, boolean visible) {
+ if (rootView == null) {
+ return;
+ }
+ final int v = visible ? View.VISIBLE : View.GONE;
+ final View captionView = rootView.findViewById(getCaptionViewId());
+ captionView.setVisibility(v);
+ }
+
int getCaptionHeightId(@WindowingMode int windowingMode) {
return Resources.ID_NULL;
}
+ int getCaptionViewId() {
+ return Resources.ID_NULL;
+ }
+
/**
* Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
* registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -466,7 +511,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
public void addCaptionInset(WindowContainerTransaction wct) {
final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode());
- if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+ if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL
+ || !mIsCaptionVisible) {
return;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 8eaf5a004c0a..57aa47e85556 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -33,8 +33,12 @@ import android.view.Choreographer
import android.view.Display.DEFAULT_DISPLAY
import android.view.InputChannel
import android.view.InputMonitor
+import android.view.InsetsSource
+import android.view.InsetsState
import android.view.SurfaceControl
import android.view.SurfaceView
+import android.view.WindowInsets.Type.navigationBars
+import android.view.WindowInsets.Type.statusBars
import androidx.core.content.getSystemService
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -42,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
@@ -53,10 +58,12 @@ import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -68,6 +75,7 @@ import org.mockito.kotlin.whenever
import java.util.Optional
import java.util.function.Supplier
+
/** Tests of [DesktopModeWindowDecorViewModel] */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -80,6 +88,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock private lateinit var displayInsetsController: DisplayInsetsController
@Mock private lateinit var mockSyncQueue: SyncTransactionQueue
@Mock private lateinit var mockDesktopTasksController: DesktopTasksController
@Mock private lateinit var mockInputMonitor: InputMonitor
@@ -97,6 +106,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
@Before
@@ -111,6 +121,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockTaskOrganizer,
mockDisplayController,
mockShellController,
+ displayInsetsController,
mockSyncQueue,
mockTransitions,
Optional.of(mockDesktopTasksController),
@@ -131,6 +142,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1])
shellInit.init()
+
+ val listenerCaptor =
+ argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+ verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
+ desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
}
@Test
@@ -274,6 +290,67 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(decoration).addTransitionPausingRelayout(transition)
}
+ @Test
+ fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add status bar insets source
+ val insetsState = InsetsState()
+ val statusBarInsetsSourceId = 0
+ val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+ statusBarInsetsSource.isVisible = false
+ insetsState.addSource(statusBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout occurs when status bar inset visibility changes
+ verify(decoration, times(1)).relayout(task)
+ }
+
+ @Test
+ fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add navigation bar insets source
+ val insetsState = InsetsState()
+ val navigationBarInsetsSourceId = 1
+ val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
+ navigationBarInsetsSource.isVisible = false
+ insetsState.addSource(navigationBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout does not occur when non-status bar inset changes visibility
+ verify(decoration, never()).relayout(task)
+ }
+
+ @Test
+ fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add status bar insets source
+ val insetsState = InsetsState()
+ val statusBarInsetsSourceId = 0
+ val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+ statusBarInsetsSource.isVisible = false
+ insetsState.addSource(statusBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout runs only once when status bar inset visibility changes.
+ verify(decoration, times(1)).relayout(task)
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
@@ -313,6 +390,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
whenever(mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), eq(task), any(), any(), any(), any(), any())
).thenReturn(decoration)
+ decoration.mTaskInfo = task
+ whenever(decoration.isFocused).thenReturn(task.isFocused)
return decoration
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fcb7863429d6..8061aa3f844a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,6 +18,9 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.statusBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
@@ -25,6 +28,8 @@ import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceCon
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -51,6 +56,7 @@ import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
import android.view.Display;
+import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
@@ -94,6 +100,7 @@ public class WindowDecorationTests extends ShellTestCase {
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
private static final int CORNER_RADIUS = 20;
+ private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -118,6 +125,7 @@ public class WindowDecorationTests extends ShellTestCase {
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
+ private final InsetsState mInsetsState = new InsetsState();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
@@ -141,6 +149,11 @@ public class WindowDecorationTests extends ShellTestCase {
.create(any(), any(), any());
when(mMockSurfaceControlViewHost.getRootSurfaceControl())
.thenReturn(mMockRootSurfaceControl);
+ when(mMockView.findViewById(anyInt())).thenReturn(mMockView);
+
+ // Add status bar inset so that WindowDecoration does not think task is in immersive mode
+ mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
+ doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
}
@Test
@@ -537,12 +550,41 @@ public class WindowDecorationTests extends ShellTestCase {
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[] {1.f, 1.f, 0.f});
+ verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f});
mockitoSession.finishMocking();
}
@Test
+ public void testInsetsAddedWhenCaptionIsVisible() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build();
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
+ .isVisible());
+ assertTrue(mInsetsState.sourceSize() == 1);
+ assertTrue(mInsetsState.sourceAt(0).getType() == statusBars());
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()), any());
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()), any());
+ }
+
+ @Test
public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).strictness(LENIENT).startMocking();
@@ -581,6 +623,33 @@ public class WindowDecorationTests extends ShellTestCase {
mockitoSession.finishMocking();
}
+
+ @Test
+ public void testInsetsRemovedWhenCaptionIsHidden() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build();
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()));
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()));
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d9ed6a8fa158..5b880797b7fd 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1473,8 +1473,7 @@ public class AudioManager {
* Returns the volume group id associated to the given {@link AudioAttributes}.
*
* @param attributes The {@link AudioAttributes} to consider.
- * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given
- * {@link AudioAttributes} if found,
+ * @return audio volume group id supporting the given {@link AudioAttributes} if found,
* {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise.
*/
public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
@@ -1589,7 +1588,7 @@ public class AudioManager {
* <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
* the volume group id supporting the given {@link AudioAttributes}.
*
- * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @param groupId of the audio volume group to consider.
* @param direction The direction to adjust the volume. One of
* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
* {@link #ADJUST_SAME}.
@@ -1633,8 +1632,8 @@ public class AudioManager {
* <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
* the volume group id supporting the given {@link AudioAttributes}.
*
- * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
- * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id.
+ * @param groupId of the audio volume group to consider.
+ * @return The mute state for the given audio volume group id.
* @see #adjustVolumeGroupVolume(int, int, int)
*/
public boolean isVolumeGroupMuted(int groupId) {
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index cc9be9c5126a..880ec8fdef88 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -4,3 +4,4 @@ michaelwr@google.com
santoscordon@google.com
chaviw@google.com
nmusgrave@google.com
+dakinola@google.com
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 13f7743f8d26..2db4be86bf91 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -940,9 +940,7 @@ public final class TvInputInfo implements Parcelable {
isHardwareInput = true;
hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
isConnectedToHdmiSwitch = hdmiConnectionRelativePosition
- != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW
- && hdmiConnectionRelativePosition
- != HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
+ == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW;
} else if (mTvInputHardwareInfo != null) {
id = generateInputId(componentName, mTvInputHardwareInfo);
type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 222877bbe9e9..88f1204641ff 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -102,6 +102,8 @@
<item name="android:layout_height">36dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
+ <item name="android:paddingLeft">6dp</item>
+ <item name="android:paddingRight">6dp</item>
<item name="android:background">@drawable/btn_negative_multiple_devices</item>
<item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
</style>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 8de12d03a92a..976a3ad69901 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -172,7 +172,7 @@ public class PackageUtil {
private Bitmap getBitmapFromDrawable(Drawable drawable) {
// Create an empty bitmap with the dimensions of our drawable
- Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
// Associate it with a canvas. This canvas will draw the icon on the bitmap
@@ -183,7 +183,11 @@ public class PackageUtil {
// Scale it down if the icon is too large
if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) {
- bmp = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
+ if (scaledBitmap != bmp) {
+ bmp.recycle();
+ }
+ return scaledBitmap;
}
return bmp;
}
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index be1e6554baf1..445bdc2c1936 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -2,45 +2,17 @@
# Needed to ensure callback field references are kept in their respective
# owning classes when the downstream callback registrars only store weak refs.
-# TODO(b/264686688): Handle these cases with more targeted annotations.
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
- private com.android.keyguard.KeyguardUpdateMonitorCallback *;
- private com.android.systemui.privacy.PrivacyConfig$Callback *;
- private com.android.systemui.privacy.PrivacyItemController$Callback *;
- private com.android.systemui.settings.UserTracker$Callback *;
- private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
- private com.android.systemui.util.service.Observer$Callback *;
- private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
-}
-# Note that these rules are temporary companions to the above rules, required
-# for cases like Kotlin where fields with anonymous types use the anonymous type
-# rather than the supertype.
--if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
- <1> *;
-}
--if class * extends com.android.systemui.privacy.PrivacyConfig$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
- <1> *;
-}
--if class * extends com.android.systemui.privacy.PrivacyItemController$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
- <1> *;
-}
--if class * extends com.android.systemui.settings.UserTracker$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
- <1> *;
-}
--if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
- <1> *;
-}
--if class * extends com.android.systemui.util.service.Observer$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+# Note that we restrict this to SysUISingleton classes, as other registering
+# classes should either *always* unregister or *never* register from their
+# constructor. We also keep callback class names for easier debugging.
+-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
+-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
+-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
<1> *;
}
--if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+-if class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
<1> *;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 50be97ec1af9..3585feb3442d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -44,8 +44,6 @@ import android.util.AttributeSet;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -66,18 +64,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController;
*/
public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
- private final int mDisappearYTranslation;
-
- private static final long IME_DISAPPEAR_DURATION_MS = 125;
-
- // A delay constant to be used in a workaround for the situation where InputMethodManagerService
- // is not switched to the new user yet.
- // TODO: Remove this by ensuring such a race condition never happens.
-
private TextView mPasswordEntry;
private TextViewInputDisabler mPasswordEntryDisabler;
- private Interpolator mLinearOutSlowInInterpolator;
- private Interpolator mFastOutLinearInInterpolator;
private DisappearAnimationListener mDisappearAnimationListener;
@Nullable private MotionLayout mContainerMotionLayout;
private boolean mAlreadyUsingSplitBouncer = false;
@@ -93,12 +81,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
public KeyguardPasswordView(Context context, AttributeSet attrs) {
super(context, attrs);
- mDisappearYTranslation = getResources().getDimensionPixelSize(
- R.dimen.disappear_y_translation);
- mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
- context, android.R.interpolator.linear_out_slow_in);
- mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
- context, android.R.interpolator.fast_out_linear_in);
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 8717a532b43d..d2d051735643 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -31,6 +31,7 @@ import android.telephony.PinResult;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -39,9 +40,9 @@ import android.widget.ImageView;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
public class KeyguardSimPinViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -324,7 +325,11 @@ public class KeyguardSimPinViewController
} else {
SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
- msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+ if (!TextUtils.isEmpty(displayName)) {
+ msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+ } else {
+ msg = rez.getString(R.string.kg_sim_pin_instructions);
+ }
if (info != null) {
color = info.getIconTint();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 248b7afd7535..b52a36b8199e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -29,6 +29,7 @@ import android.telephony.PinResult;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManager;
import android.widget.ImageView;
@@ -36,9 +37,9 @@ import android.widget.ImageView;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
public class KeyguardSimPukViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -206,7 +207,11 @@ public class KeyguardSimPukViewController
} else {
SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
CharSequence displayName = info != null ? info.getDisplayName() : "";
- msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+ if (!TextUtils.isEmpty(displayName)) {
+ msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+ } else {
+ msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+ }
if (info != null) {
color = info.getIconTint();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7b596328ca13..247606771155 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -25,12 +25,14 @@ import androidx.annotation.Nullable;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.plugins.WeatherData;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
import java.util.TimeZone;
/**
* Callback for general information relevant to lock screen.
*/
+@WeaklyReferencedCallback
public class KeyguardUpdateMonitorCallback {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index fde92b85cac3..0bac40bcbcc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -451,7 +451,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
if (!keyguardStateController.isKeyguardGoingAway &&
willUnlockWithInWindowLauncherAnimations) {
try {
- launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+ launcherUnlockController?.setUnlockAmount(1f,
+ biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
} catch (e: DeadObjectException) {
Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
"onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index a1291a4d6b0f..724241d8d41f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -19,6 +19,7 @@ package com.android.systemui.media.controls.pipeline
import android.content.Context
import android.os.SystemProperties
import android.util.Log
+import com.android.internal.annotations.KeepForWeakReference
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
@@ -82,6 +83,8 @@ constructor(
private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
private var reactivatedKey: String? = null
+ // Ensure the field (and associated reference) isn't removed during optimization.
+ @KeepForWeakReference
private val userTrackerCallback =
object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
index d949a2a0afe5..67d390d4f10d 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
@@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.asIndenting
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.withIncreasedIndent
import java.io.PrintWriter
@@ -144,6 +145,7 @@ class PrivacyConfig @Inject constructor(
ipw.flush()
}
+ @WeaklyReferencedCallback
interface Callback {
fun onFlagMicCameraChanged(flag: Boolean) {}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index bd592c9daff6..cf1fbe3685e3 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -16,6 +16,8 @@
package com.android.systemui.settings
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
+
import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
@@ -64,6 +66,7 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
/**
* Callback for notifying of changes.
*/
+ @WeaklyReferencedCallback
interface Callback {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index 6dc8065b2822..da91d6a05d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar.phone;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+
+@WeaklyReferencedCallback
public interface StatusBarWindowCallback {
/**
* Invoked when the internal state of NotificationShadeWindowControllerImpl changes.
diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java
new file mode 100644
index 000000000000..855bba6cfd24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java
@@ -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.util.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Descriptive annotation for clearly tagging callback types that are weakly
+ * referenced during registration.
+ *
+ * This is useful in providing hints to Proguard about certain fields that
+ * should be kept to preserve strong references.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({TYPE})
+public @interface WeaklyReferencedCallback {}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 968dcc95ef50..df5162af70c5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,6 +26,7 @@ import android.util.Log;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -64,6 +65,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
* An interface for listening to the connection status.
* @param <T> The wrapper type.
*/
+ @WeaklyReferencedCallback
public interface Callback<T> {
/**
* Invoked when the service has been successfully connected to.
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
index 768743217cc7..425336d540f5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
@@ -16,6 +16,8 @@
package com.android.systemui.util.service;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+
/**
* The {@link Observer} interface specifies an entity which listeners
* can be informed of changes to the source, which will require updating. Note that this deals
@@ -25,6 +27,7 @@ public interface Observer {
/**
* Callback for receiving updates from the {@link Observer}.
*/
+ @WeaklyReferencedCallback
interface Callback {
/**
* Invoked when the source has changed.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 553b08501925..0956c6ded013 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -409,6 +409,13 @@ public final class ActiveServices {
AppWidgetManagerInternal mAppWidgetManagerInternal;
+ /**
+ * The available ANR timers.
+ */
+ private final ProcessAnrTimer mActiveServiceAnrTimer;
+ private final ServiceAnrTimer mShortFGSAnrTimer;
+ private final ServiceAnrTimer mServiceFGAnrTimer;
+
// allowlisted packageName.
ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
@@ -663,6 +670,15 @@ public final class ActiveServices {
final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
this.mFGSLogger = new ForegroundServiceTypeLoggerModule();
+ this.mActiveServiceAnrTimer = new ProcessAnrTimer(service,
+ ActivityManagerService.SERVICE_TIMEOUT_MSG,
+ "SERVICE_TIMEOUT");
+ this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
+ ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
+ "FGS_TIMEOUT");
+ this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
+ ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
+ "SERVICE_FOREGROUND_TIMEOUT");
}
void systemServicesReady() {
@@ -2083,8 +2099,7 @@ public final class ActiveServices {
r.fgRequired = false;
r.fgWaiting = false;
alreadyStartedOp = stopProcStatsOp = true;
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+ mServiceFGAnrTimer.cancel(r);
}
final ProcessServiceRecord psr = r.app.mServices;
@@ -3313,7 +3328,7 @@ public final class ActiveServices {
}
void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+ mShortFGSAnrTimer.cancel(sr);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG,
sr);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
@@ -3387,9 +3402,11 @@ public final class ActiveServices {
Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr
+ " " + sr.getShortFgsTimedEventDescription(nowUptime));
}
+ mShortFGSAnrTimer.discard(sr);
return;
}
Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+ mShortFGSAnrTimer.accept(sr);
traceInstant("short FGS timeout: ", sr);
logFGSStateChangeLocked(sr,
@@ -3413,11 +3430,10 @@ public final class ActiveServices {
msg, sr.getShortFgsInfo().getProcStateDemoteTime());
}
- {
- final Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
- mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
- }
+ // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now".
+ // Compute the time from "now" when starting the anr timer.
+ mShortFGSAnrTimer.start(sr,
+ sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis());
}
}
@@ -4847,8 +4863,7 @@ public final class ActiveServices {
// a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT
// again.
if (r.fgRequired && r.fgWaiting) {
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+ mServiceFGAnrTimer.cancel(r);
r.fgWaiting = false;
}
@@ -5691,8 +5706,7 @@ public final class ActiveServices {
}
mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+ mServiceFGAnrTimer.cancel(r);
if (r.app != null) {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
@@ -6128,7 +6142,7 @@ public final class ActiveServices {
if (psr.numberOfExecutingServices() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
"No more executingServices of " + r.shortInstanceName);
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
+ if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app);
} else if (r.executeFg) {
// Need to re-evaluate whether the app still needs to be in the foreground.
for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
@@ -6816,13 +6830,16 @@ public final class ActiveServices {
synchronized (mAm) {
if (proc.isDebugging()) {
// The app's being debugged, ignore timeout.
+ mActiveServiceAnrTimer.discard(proc);
return;
}
final ProcessServiceRecord psr = proc.mServices;
if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null
|| proc.isKilled()) {
+ mActiveServiceAnrTimer.discard(proc);
return;
}
+ mActiveServiceAnrTimer.accept(proc);
final long now = SystemClock.uptimeMillis();
final long maxTime = now
- (psr.shouldExecServicesFg()
@@ -6855,12 +6872,11 @@ public final class ActiveServices {
timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName,
waitedMillis);
} else {
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_TIMEOUT_MSG);
- msg.obj = proc;
- mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
- ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
- (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT));
+ final long delay = psr.shouldExecServicesFg()
+ ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
+ (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)
+ - SystemClock.uptimeMillis();
+ mActiveServiceAnrTimer.start(proc, delay);
}
}
@@ -6886,12 +6902,15 @@ public final class ActiveServices {
synchronized (mAm) {
timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
if (!r.fgRequired || !r.fgWaiting || r.destroying) {
+ mServiceFGAnrTimer.discard(r);
return;
}
+ mServiceFGAnrTimer.accept(r);
app = r.app;
if (app != null && app.isDebugging()) {
// The app's being debugged; let it ride
+ mServiceFGAnrTimer.discard(r);
return;
}
@@ -6948,26 +6967,46 @@ public final class ActiveServices {
ForegroundServiceDidNotStartInTimeException.createExtrasForService(service));
}
+ private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> {
+
+ ProcessAnrTimer(ActivityManagerService am, int msg, String label) {
+ super(Objects.requireNonNull(am).mHandler, msg, label);
+ }
+
+ void start(@NonNull ProcessRecord proc, long millis) {
+ start(proc, proc.getPid(), proc.uid, millis);
+ }
+ }
+
+ private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> {
+
+ ServiceAnrTimer(ActivityManagerService am, int msg, String label) {
+ super(Objects.requireNonNull(am).mHandler, msg, label);
+ }
+
+ void start(@NonNull ServiceRecord service, long millis) {
+ start(service,
+ (service.app != null) ? service.app.getPid() : 0,
+ service.appInfo.uid,
+ millis);
+ }
+ }
+
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
return;
}
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_TIMEOUT_MSG);
- msg.obj = proc;
- mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
- ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
+ final long delay = proc.mServices.shouldExecServicesFg()
+ ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT;
+ mActiveServiceAnrTimer.start(proc, delay);
}
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
return;
}
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
- msg.obj = r;
r.fgWaiting = true;
- mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);
+ mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs);
}
final class ServiceDumper {
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java
index 9ba49ce35dad..3e17930e3cb9 100644
--- a/services/core/java/com/android/server/am/AnrTimer.java
+++ b/services/core/java/com/android/server/am/AnrTimer.java
@@ -28,6 +28,7 @@ import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
import android.text.TextUtils;
+import android.text.format.TimeMigrationUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -44,7 +45,6 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Date;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -150,7 +150,7 @@ class AnrTimer<V> {
/** A partial stack that localizes the caller of the operation. */
final StackTraceElement[] stack;
/** The date, in local time, the error was created. */
- final String date;
+ final long timestamp;
Error(@NonNull String issue, @NonNull String operation, @NonNull String tag,
@NonNull StackTraceElement[] stack, @NonNull String arg) {
@@ -159,7 +159,7 @@ class AnrTimer<V> {
this.tag = tag;
this.stack = stack;
this.arg = arg;
- this.date = new Date().toString();
+ this.timestamp = SystemClock.elapsedRealtime();
}
}
@@ -347,20 +347,23 @@ class AnrTimer<V> {
* main Looper.
*/
@NonNull
- Handler getHandler(@NonNull Handler.Callback callback) {
+ Handler newHandler(@NonNull Handler.Callback callback) {
Looper looper = mReferenceHandler.getLooper();
if (looper == null) looper = Looper.getMainLooper();
return new Handler(looper, callback);
- };
+ }
- /** Return a CpuTracker. */
+ /**
+ * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
+ * for unit tests.
+ **/
@NonNull
- CpuTracker getTracker() {
+ CpuTracker newTracker() {
return new CpuTracker();
}
/** Return true if the feature is enabled. */
- boolean getFeatureEnabled() {
+ boolean isFeatureEnabled() {
return anrTimerServiceEnabled();
}
}
@@ -401,8 +404,8 @@ class AnrTimer<V> {
/** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
@VisibleForTesting
HandlerTimerService(@NonNull Injector injector) {
- mHandler = injector.getHandler(this::expires);
- mCpu = injector.getTracker();
+ mHandler = injector.newHandler(this::expires);
+ mCpu = injector.newTracker();
}
/** Post a message with the specified timeout. The timer is not modified. */
@@ -513,7 +516,26 @@ class AnrTimer<V> {
private final FeatureSwitch mFeature;
/**
- * The common constructor. A null injector results in a normal, production timer.
+ * Create one AnrTimer instance. The instance is given a handler and a "what". Individual
+ * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent
+ * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+ * to the timer key.
+ *
+ * AnrTimer instances have a label, which must be unique. The label is used for reporting and
+ * debug.
+ *
+ * If an individual timer expires internally, and the "extend" parameter is true, then the
+ * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+ * the client. The extension policy is not part of the instance.
+ *
+ * This method accepts an {@link #Injector} to tune behavior for testing. This method should
+ * not be called directly by regular clients.
+ *
+ * @param handler The handler to which the expiration message will be delivered.
+ * @param what The "what" parameter for the expiration message.
+ * @param label A name for this instance.
+ * @param extend A flag to indicate if expired timers can be granted extensions.
+ * @param injector An {@link #Injector} to tune behavior for testing.
*/
@VisibleForTesting
AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
@@ -522,7 +544,7 @@ class AnrTimer<V> {
mWhat = what;
mLabel = label;
mExtend = extend;
- boolean enabled = injector.getFeatureEnabled();
+ boolean enabled = injector.isFeatureEnabled();
if (!enabled) {
mFeature = new FeatureDisabled();
mTimerService = null;
@@ -538,14 +560,25 @@ class AnrTimer<V> {
}
/**
- * Create one timer instance for production. The client can ask for extensible timeouts.
+ * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler,
+ * int, String, boolean, Injector} for a functional description.
+ *
+ * @param handler The handler to which the expiration message will be delivered.
+ * @param what The "what" parameter for the expiration message.
+ * @param label A name for this instance.
+ * @param extend A flag to indicate if expired timers can be granted extensions.
*/
AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
this(handler, what, label, extend, new Injector(handler));
}
/**
- * Create one timer instance for production. There are no extensible timeouts.
+ * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled.
+ * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description.
+ *
+ * @param handler The handler to which the expiration message will be delivered.
+ * @param what The "what" parameter for the expiration message.
+ * @param label A name for this instance.
*/
AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
this(handler, what, label, false);
@@ -555,6 +588,8 @@ class AnrTimer<V> {
* Return true if the service is enabled on this instance. Clients should use this method to
* decide if the feature is enabled, and not read the flags directly. This method should be
* deleted if and when the feature is enabled permanently.
+ *
+ * @return true if the service is flag-enabled.
*/
boolean serviceEnabled() {
return mFeature.enabled();
@@ -642,7 +677,7 @@ class AnrTimer<V> {
}
/**
- * Report something about a timer.
+ * Generate a log message for a timer.
*/
private void report(@NonNull Timer timer, @NonNull String msg) {
Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
@@ -654,9 +689,13 @@ class AnrTimer<V> {
*/
private abstract class FeatureSwitch {
abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+
abstract boolean cancel(@NonNull V arg);
+
abstract boolean accept(@NonNull V arg);
+
abstract boolean discard(@NonNull V arg);
+
abstract boolean enabled();
}
@@ -666,6 +705,7 @@ class AnrTimer<V> {
*/
private class FeatureDisabled extends FeatureSwitch {
/** Start a timer by sending a message to the client's handler. */
+ @Override
boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
final Message msg = mHandler.obtainMessage(mWhat, arg);
mHandler.sendMessageDelayed(msg, timeoutMs);
@@ -673,22 +713,26 @@ class AnrTimer<V> {
}
/** Cancel a timer by removing the message from the client's handler. */
+ @Override
boolean cancel(@NonNull V arg) {
mHandler.removeMessages(mWhat, arg);
return true;
}
/** accept() is a no-op when the feature is disabled. */
+ @Override
boolean accept(@NonNull V arg) {
return true;
}
/** discard() is a no-op when the feature is disabled. */
+ @Override
boolean discard(@NonNull V arg) {
return true;
}
/** The feature is not enabled. */
+ @Override
boolean enabled() {
return false;
}
@@ -703,16 +747,17 @@ class AnrTimer<V> {
/**
* Start a timer.
*/
+ @Override
boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
synchronized (mLock) {
Timer old = mTimerMap.get(arg);
+ // There is an existing timer. If the timer was running, then cancel the running
+ // timer and restart it. If the timer was expired record a protocol error and
+ // discard the expired timer.
if (old != null) {
- // There is an existing timer. This is a protocol error in the client.
- // Record the error and then clean up by canceling running timers and
- // discarding expired timers.
- restartedLocked(old.status, arg);
if (old.status == TIMER_EXPIRED) {
+ restartedLocked(old.status, arg);
discard(arg);
} else {
cancel(arg);
@@ -735,6 +780,7 @@ class AnrTimer<V> {
/**
* Cancel a timer. Return false if the timer was not found.
*/
+ @Override
boolean cancel(@NonNull V arg) {
synchronized (mLock) {
Timer timer = removeLocked(arg);
@@ -755,6 +801,7 @@ class AnrTimer<V> {
* Accept a timer in the framework-level handler. The timeout has been accepted and the
* timeout handler is executing. Return false if the timer was not found.
*/
+ @Override
boolean accept(@NonNull V arg) {
synchronized (mLock) {
Timer timer = removeLocked(arg);
@@ -775,6 +822,7 @@ class AnrTimer<V> {
* longer interesting. No statistics are collected. Return false if the time was not
* found.
*/
+ @Override
boolean discard(@NonNull V arg) {
synchronized (mLock) {
Timer timer = removeLocked(arg);
@@ -791,40 +839,58 @@ class AnrTimer<V> {
}
/** The feature is enabled. */
+ @Override
boolean enabled() {
return true;
}
}
/**
- * Start a timer associated with arg. If a timer already exists with the same arg, then that
- * timer is canceled and a new timer is created. This returns false if the timer cannot be
- * created.
+ * Start a timer associated with arg. The same object must be used to cancel, accept, or
+ * discard a timer later. If a timer already exists with the same arg, then the existing timer
+ * is canceled and a new timer is created.
+ *
+ * @param arg The key by which the timer is known. This is never examined or modified.
+ * @param pid The Linux process ID of the target being timed.
+ * @param uid The Linux user ID of the target being timed.
+ * @param timeoutMs The timer timeout, in milliseconds.
+ * @return true if the timer was successfully created.
*/
boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
return mFeature.start(arg, pid, uid, timeoutMs);
}
/**
- * Cancel a running timer and remove it from any list. This returns true if the timer was
- * found and false otherwise. It is not an error to cancel a non-existent timer. It is also
- * not an error to cancel an expired timer.
+ * Cancel the running timer associated with arg. The timer is forgotten. If the timer has
+ * expired, the call is treated as a discard. No errors are reported if the timer does not
+ * exist or if the timer has expired.
+ *
+ * @return true if the timer was found and was running.
*/
boolean cancel(@NonNull V arg) {
return mFeature.cancel(arg);
}
/**
- * Accept an expired timer. This returns false if the timer was not found or if the timer was
- * not expired.
+ * Accept the expired timer associated with arg. This indicates that the caller considers the
+ * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is
+ * an error to accept a running timer, however the running timer will be canceled.
+ *
+ * @return true if the timer was found and was expired.
*/
boolean accept(@NonNull V arg) {
return mFeature.accept(arg);
}
/**
- * Discard an expired timer. This returns false if the timer was not found or if the timer was
- * not expired.
+ * Discard the expired timer associated with arg. This indicates that the caller considers the
+ * timer expiration to be a false ANR. ((See {@link #accept} for an alternate response.) One
+ * reason to discard an expired timer is if the process being timed was also being debugged:
+ * such a process could be stopped at a breakpoint and its failure to respond would not be an
+ * error. It is an error to discard a running timer, however the running timer will be
+ * canceled.
+ *
+ * @return true if the timer was found and was expired.
*/
boolean discard(@NonNull V arg) {
return mFeature.discard(arg);
@@ -913,7 +979,10 @@ class AnrTimer<V> {
private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
err.issue, err.arg);
- ipw.format(" date:%s\n", err.date);
+
+ final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ final long etime = offset + err.timestamp;
+ ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
ipw.increaseIndent();
for (int i = 0; i < err.stack.length; i++) {
ipw.println(" " + err.stack[i].toString());
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a42890707368..d19eae5b0709 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -258,7 +258,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6;
private static final int MSG_UID_STATE_CHANGED = 7;
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This constant should be deleted if and
+ // when the flag is fused on.
private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
private void enqueueUpdateRunningList() {
@@ -274,7 +275,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
updateRunningList();
return true;
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This case should be deleted if
+ // and when the flag is fused on.
case MSG_DELIVERY_TIMEOUT_SOFT: {
synchronized (mService) {
deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
@@ -1169,7 +1171,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.resultTo = null;
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a
+ // single call to {@code mAnrTimer.start()} if and when the flag is fused on.
private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue,
int softTimeoutMillis) {
if (mAnrTimer.serviceEnabled()) {
@@ -1181,7 +1184,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a
+ // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on.
private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
mAnrTimer.cancel(queue);
if (!mAnrTimer.serviceEnabled()) {
@@ -1189,7 +1193,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This function can be deleted entirely
+ // if and when the flag is fused on.
private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
int softTimeoutMillis) {
if (queue.app != null) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 6c5f3e74b0d2..d65c7c2c526d 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -189,6 +189,8 @@ public class SoundDoseHelper {
private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
+ private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false);
+
private final Object mCsdAsAFeatureLock = new Object();
@GuardedBy("mCsdAsAFeatureLock")
@@ -375,9 +377,21 @@ public class SoundDoseHelper {
}
}
+ private boolean updateCsdForTestApi() {
+ if (mForceCsdProperty.get() != SystemProperties.getBoolean(
+ SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) {
+ updateCsdEnabled("SystemPropertiesChangeCallback");
+ }
+
+ return mEnableCsd.get();
+ }
+
float getCsd() {
if (!mEnableCsd.get()) {
- return -1.f;
+ // since this will only be called by a test api enable csd if system property is set
+ if (!updateCsdForTestApi()) {
+ return -1.f;
+ }
}
final ISoundDose soundDose = mSoundDose.get();
@@ -396,7 +410,10 @@ public class SoundDoseHelper {
void setCsd(float csd) {
if (!mEnableCsd.get()) {
- return;
+ // since this will only be called by a test api enable csd if system property is set
+ if (!updateCsdForTestApi()) {
+ return;
+ }
}
SoundDoseRecord[] doseRecordsArray;
@@ -430,7 +447,10 @@ public class SoundDoseHelper {
void resetCsdTimeouts() {
if (!mEnableCsd.get()) {
- return;
+ // since this will only be called by a test api enable csd if system property is set
+ if (!updateCsdForTestApi()) {
+ return;
+ }
}
synchronized (mCsdStateLock) {
@@ -440,7 +460,10 @@ public class SoundDoseHelper {
void forceUseFrameworkMel(boolean useFrameworkMel) {
if (!mEnableCsd.get()) {
- return;
+ // since this will only be called by a test api enable csd if system property is set
+ if (!updateCsdForTestApi()) {
+ return;
+ }
}
final ISoundDose soundDose = mSoundDose.get();
@@ -458,7 +481,10 @@ public class SoundDoseHelper {
void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
if (!mEnableCsd.get()) {
- return;
+ // since this will only be called by a test api enable csd if system property is set
+ if (!updateCsdForTestApi()) {
+ return;
+ }
}
final ISoundDose soundDose = mSoundDose.get();
@@ -488,7 +514,7 @@ public class SoundDoseHelper {
try {
return soundDose.isSoundDoseHalSupported();
} catch (RemoteException e) {
- Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
+ Log.e(TAG, "Exception while querying the csd enabled status", e);
}
return false;
}
@@ -544,7 +570,7 @@ public class SoundDoseHelper {
audioDeviceCategory.csdCompatible = isHeadphone;
soundDose.setAudioDeviceCategory(audioDeviceCategory);
} catch (RemoteException e) {
- Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+ Log.e(TAG, "Exception while setting the audio device category", e);
}
}
@@ -894,7 +920,7 @@ public class SoundDoseHelper {
mCachedAudioDeviceCategories.clear();
}
} catch (RemoteException e) {
- Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+ Log.e(TAG, "Exception while initializing the cached audio device categories", e);
}
synchronized (mCsdAsAFeatureLock) {
@@ -991,19 +1017,20 @@ public class SoundDoseHelper {
}
private void updateCsdEnabled(String caller) {
- boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false);
+ mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE,
+ false));
// we are using the MCC overlaid legacy flag used for the safe volume enablement
// to determine whether the MCC enforces any safe hearing standard.
boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_safe_media_volume_enabled);
boolean csdEnable = mContext.getResources().getBoolean(
R.bool.config_safe_sound_dosage_enabled);
- boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce;
+ boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get();
synchronized (mCsdAsAFeatureLock) {
if (!mccEnforcedSafeMedia && csdEnable) {
mIsCsdAsAFeatureAvailable = true;
- newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce;
+ newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get();
Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: "
+ newEnabledCsd);
} else {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 4538cad513d6..1760bb3c7a6a 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IAuthService;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IInvalidationCallback;
@@ -357,6 +358,18 @@ public class AuthService extends SystemService {
}
@Override
+ public void registerBiometricPromptStatusListener(
+ IBiometricPromptStatusListener listener) throws RemoteException {
+ checkInternalPermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mBiometricService.registerBiometricPromptStatusListener(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void invalidateAuthenticatorIds(int userId, int fromSensorId,
IInvalidationCallback callback) throws RemoteException {
checkInternalPermission();
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 1898b8015462..9569f23e8d49 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -88,6 +89,7 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
@@ -105,6 +107,8 @@ public class BiometricService extends SystemService {
@VisibleForTesting
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
+ private final ConcurrentLinkedQueue<BiometricPromptStatusListener>
+ mBiometricPromptStatusListeners;
private final Random mRandom = new Random();
@NonNull private final Supplier<Long> mRequestCounter;
@NonNull private final BiometricContext mBiometricContext;
@@ -425,6 +429,42 @@ public class BiometricService extends SystemService {
}
}
+ final class BiometricPromptStatusListener implements IBinder.DeathRecipient {
+ private final IBiometricPromptStatusListener mBiometricPromptStatusListener;
+
+ BiometricPromptStatusListener(IBiometricPromptStatusListener callback) {
+ mBiometricPromptStatusListener = callback;
+ }
+
+ void notifyBiometricPromptShowing() {
+ try {
+ mBiometricPromptStatusListener.onBiometricPromptShowing();
+ } catch (DeadObjectException e) {
+ Slog.w(TAG, "Death while invoking notifyHandleAuthenticate", e);
+ mBiometricPromptStatusListeners.remove(this);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke notifyHandleAuthenticate", e);
+ }
+ }
+
+ void notifyBiometricPromptIdle() {
+ try {
+ mBiometricPromptStatusListener.onBiometricPromptIdle();
+ } catch (DeadObjectException e) {
+ Slog.w(TAG, "Death while invoking notifyDialogDismissed", e);
+ mBiometricPromptStatusListeners.remove(this);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke notifyDialogDismissed", e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ Slog.e(TAG, "Biometric prompt callback binder died");
+ mBiometricPromptStatusListeners.remove(this);
+ }
+ }
+
// Receives events from individual biometric sensors.
private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) {
return new IBiometricSensorReceiver.Stub() {
@@ -705,6 +745,22 @@ public class BiometricService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
+ public void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback) {
+ super.registerBiometricPromptStatusListener_enforcePermission();
+
+ BiometricPromptStatusListener biometricPromptStatusListener =
+ new BiometricPromptStatusListener(callback);
+ mBiometricPromptStatusListeners.add(biometricPromptStatusListener);
+
+ if (mAuthSession != null) {
+ biometricPromptStatusListener.notifyBiometricPromptShowing();
+ } else {
+ biometricPromptStatusListener.notifyBiometricPromptIdle();
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override // Binder call
public void invalidateAuthenticatorIds(int userId, int fromSensorId,
IInvalidationCallback callback) {
@@ -1044,6 +1100,7 @@ public class BiometricService extends SystemService {
mDevicePolicyManager = mInjector.getDevicePolicyManager(context);
mImpl = new BiometricServiceWrapper();
mEnabledOnKeyguardCallbacks = new ArrayList<>();
+ mBiometricPromptStatusListeners = new ConcurrentLinkedQueue<>();
mSettingObserver = mInjector.getSettingObserver(context, mHandler,
mEnabledOnKeyguardCallbacks);
mRequestCounter = mInjector.getRequestGenerator();
@@ -1158,6 +1215,7 @@ public class BiometricService extends SystemService {
if (finished) {
Slog.d(TAG, "handleOnError: AuthSession finished");
mAuthSession = null;
+ notifyAuthSessionChanged();
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
@@ -1186,6 +1244,7 @@ public class BiometricService extends SystemService {
session.onDialogDismissed(reason, credentialAttestation);
mAuthSession = null;
+ notifyAuthSessionChanged();
}
private void handleOnTryAgainPressed(long requestId) {
@@ -1235,6 +1294,7 @@ public class BiometricService extends SystemService {
final boolean finished = session.onClientDied();
if (finished) {
mAuthSession = null;
+ notifyAuthSessionChanged();
}
}
@@ -1349,6 +1409,16 @@ public class BiometricService extends SystemService {
});
}
+ private void notifyAuthSessionChanged() {
+ for (BiometricPromptStatusListener listener : mBiometricPromptStatusListeners) {
+ if (mAuthSession == null) {
+ listener.notifyBiometricPromptIdle();
+ } else {
+ listener.notifyBiometricPromptShowing();
+ }
+ }
+ }
+
/**
* handleAuthenticate() (above) which is called from BiometricPrompt determines which
* modality/modalities to start authenticating with. authenticateInternal() should only be
@@ -1386,6 +1456,7 @@ public class BiometricService extends SystemService {
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
+ notifyAuthSessionChanged();
}
private void handleCancelAuthentication(long requestId) {
@@ -1400,6 +1471,7 @@ public class BiometricService extends SystemService {
if (finished) {
Slog.d(TAG, "handleCancelAuthentication: AuthSession finished");
mAuthSession = null;
+ notifyAuthSessionChanged();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d372f3031b81..0689478ded1e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -160,6 +160,7 @@ import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.utils.FoldSettingProvider;
@@ -522,6 +523,8 @@ public final class DisplayManagerService extends SystemService {
private final DisplayManagerFlags mFlags;
+ private final DisplayNotificationManager mDisplayNotificationManager;
+
/**
* Applications use {@link android.view.Display#getRefreshRate} and
* {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -555,6 +558,7 @@ public final class DisplayManagerService extends SystemService {
mInjector = injector;
mContext = context;
mFlags = injector.getFlags();
+ mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext);
mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
@@ -650,6 +654,7 @@ public final class DisplayManagerService extends SystemService {
}
mDisplayModeDirector.onBootCompleted();
mLogicalDisplayMapper.onBootCompleted();
+ mDisplayNotificationManager.onBootCompleted();
}
}
@@ -784,6 +789,10 @@ public final class DisplayManagerService extends SystemService {
}
}
+ DisplayNotificationManager getDisplayNotificationManager() {
+ return mDisplayNotificationManager;
+ }
+
private void loadStableDisplayValuesLocked() {
final Point size = mPersistentDataStore.getStableDisplaySize();
if (size.x > 0 && size.y > 0) {
@@ -1776,7 +1785,8 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
// main display adapter
registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext,
- mHandler, mDisplayDeviceRepo, mFlags));
+ mHandler, mDisplayDeviceRepo, mFlags,
+ mDisplayNotificationManager));
// Standalone VR devices rely on a virtual display as their primary display for
// 2D UI. We register virtual display adapter along side the main display adapter
@@ -3191,9 +3201,10 @@ public final class DisplayManagerService extends SystemService {
LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
Handler handler, DisplayAdapter.Listener displayAdapterListener,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags,
+ DisplayNotificationManager displayNotificationManager) {
return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
- flags);
+ flags, displayNotificationManager);
}
long getDefaultDisplayDelayTimeout() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9b022d8b6662..d97c8e71c73c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -30,6 +30,8 @@ import java.util.Arrays;
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
+ private static final String NOTIFICATION_TYPES =
+ "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable";
private final DisplayManagerService mService;
private final DisplayManagerFlags mFlags;
@@ -46,6 +48,10 @@ class DisplayManagerShellCommand extends ShellCommand {
}
final PrintWriter pw = getOutPrintWriter();
switch(cmd) {
+ case "show-notification":
+ return showNotification();
+ case "cancel-notifications":
+ return cancelNotifications();
case "set-brightness":
return setBrightness();
case "reset-brightness-configuration":
@@ -102,6 +108,10 @@ class DisplayManagerShellCommand extends ShellCommand {
pw.println(" help");
pw.println(" Print this help text.");
pw.println();
+ pw.println(" show-notification NOTIFICATION_TYPE");
+ pw.println(" Show notification for one of the following types: " + NOTIFICATION_TYPES);
+ pw.println(" cancel-notifications");
+ pw.println(" Cancel notifications.");
pw.println(" set-brightness BRIGHTNESS");
pw.println(" Sets the current brightness to BRIGHTNESS (a number between 0 and 1).");
pw.println(" reset-brightness-configuration");
@@ -172,6 +182,39 @@ class DisplayManagerShellCommand extends ShellCommand {
return 0;
}
+ private int showNotification() {
+ final String notificationType = getNextArg();
+ if (notificationType == null) {
+ getErrPrintWriter().println("Error: no notificationType specified, use one of: "
+ + NOTIFICATION_TYPES);
+ return 1;
+ }
+
+ switch(notificationType) {
+ case "on-hotplug-error":
+ mService.getDisplayNotificationManager().onHotplugConnectionError();
+ break;
+ case "on-link-training-failure":
+ mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure();
+ break;
+ case "on-cable-dp-incapable":
+ mService.getDisplayNotificationManager().onCableNotCapableDisplayPort();
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: unexpected notification type=" + notificationType + ", use one of: "
+ + NOTIFICATION_TYPES);
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private int cancelNotifications() {
+ mService.getDisplayNotificationManager().cancelNotifications();
+ return 0;
+ }
+
private int setBrightness() {
String brightnessText = getNextArg();
if (brightnessText == null) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0a1f316ac059..e5d38cb669d4 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -22,9 +22,8 @@ import static android.view.Display.Mode.INVALID_MODE_ID;
import android.app.ActivityThread;
import android.content.Context;
import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.hardware.sidekick.SidekickInternal;
import android.os.Build;
import android.os.Handler;
@@ -52,6 +51,7 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
@@ -86,18 +86,25 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private final DisplayManagerFlags mFlags;
+ private final DisplayNotificationManager mDisplayNotificationManager;
+
private Context mOverlayContext;
// Called with SyncRoot lock held.
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
- Handler handler, Listener listener, DisplayManagerFlags flags) {
- this(syncRoot, context, handler, listener, flags, new Injector());
+ Handler handler, Listener listener, DisplayManagerFlags flags,
+ DisplayNotificationManager displayNotificationManager) {
+ this(syncRoot, context, handler, listener, flags, displayNotificationManager,
+ new Injector());
}
@VisibleForTesting
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler,
- Listener listener, DisplayManagerFlags flags, Injector injector) {
+ Listener listener, DisplayManagerFlags flags,
+ DisplayNotificationManager displayNotificationManager,
+ Injector injector) {
super(syncRoot, context, handler, listener, TAG);
+ mDisplayNotificationManager = displayNotificationManager;
mInjector = injector;
mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport();
@@ -1454,6 +1461,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
+ "timestampNanos=" + timestampNanos
+ ", connectionError=" + connectionError + ")");
}
+
+ mDisplayNotificationManager.onHotplugConnectionError();
}
@Override
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index a5e3b70802ef..7050c5a4168f 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -59,6 +59,10 @@ public class DisplayManagerFlags {
Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
Flags::enableModeLimitForExternalDisplay);
+ private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
+ Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
+ Flags::enableConnectedDisplayErrorHandling);
+
private final FlagState mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState = new FlagState(
Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE,
Flags::backUpSmoothDisplayAndForcePeakRefreshRate);
@@ -123,6 +127,11 @@ public class DisplayManagerFlags {
return mDisplayOffloadFlagState.isEnabled();
}
+ /** Returns whether error notifications for connected displays are enabled on not */
+ public boolean isConnectedDisplayErrorHandlingEnabled() {
+ return mConnectedDisplayErrorHandlingFlagState.isEnabled();
+ }
+
public boolean isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled() {
return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
}
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3d203fb7427f..a85e10dcfe2e 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -82,6 +82,14 @@ flag {
}
flag {
+ name: "enable_connected_display_error_handling"
+ namespace: "display_manager"
+ description: "Feature flag for connected display error handling"
+ bug: "283461472"
+ is_fixed_read_only: true
+}
+
+flag {
name: "back_up_smooth_display_and_force_peak_refresh_rate"
namespace: "display_manager"
description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate"
diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
new file mode 100644
index 000000000000..f683e8104889
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
@@ -0,0 +1,132 @@
+/*
+ * 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.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Detects usb issues related to an external display connected.
+ */
+public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener {
+ private static final String TAG = "ConnectedDisplayUsbErrorsDetector";
+
+ /**
+ * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}.
+ */
+ public interface Injector {
+
+ /**
+ * @return {@link UsbManager} service.
+ */
+ UsbManager getUsbManager();
+ }
+
+ /**
+ * USB errors listener
+ */
+ public interface Listener {
+
+ /**
+ * Link training failure callback.
+ */
+ void onDisplayPortLinkTrainingFailure();
+
+ /**
+ * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort.
+ */
+ void onCableNotCapableDisplayPort();
+ }
+
+ private Listener mListener;
+ private final Injector mInjector;
+ private final Context mContext;
+ private final boolean mIsConnectedDisplayErrorHandlingEnabled;
+
+ ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+ @NonNull final Context context) {
+ this(flags, context, () -> context.getSystemService(UsbManager.class));
+ }
+
+ @VisibleForTesting
+ ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+ @NonNull final Context context, @NonNull final Injector injector) {
+ mContext = context;
+ mInjector = injector;
+ mIsConnectedDisplayErrorHandlingEnabled =
+ flags.isConnectedDisplayErrorHandlingEnabled();
+ }
+
+ /** Register listener for usb error events. */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ void registerListener(final Listener listener) {
+ if (!mIsConnectedDisplayErrorHandlingEnabled) {
+ return;
+ }
+
+ final var usbManager = mInjector.getUsbManager();
+ if (usbManager == null) {
+ Slog.e(TAG, "UsbManager is null");
+ return;
+ }
+
+ mListener = listener;
+
+ try {
+ usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Failed to register listener", e);
+ }
+ }
+
+ /**
+ * Callback upon changes in {@link DisplayPortAltModeInfo}.
+ * @param portId String describing the {@link android.hardware.usb.UsbPort} that was changed.
+ * @param info New {@link DisplayPortAltModeInfo} for the corresponding portId.
+ */
+ @Override
+ public void onDisplayPortAltModeInfoChanged(@NonNull String portId,
+ @NonNull DisplayPortAltModeInfo info) {
+ if (mListener == null) {
+ return;
+ }
+
+ if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus()
+ && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus()
+ ) {
+ mListener.onCableNotCapableDisplayPort();
+ return;
+ }
+
+ if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) {
+ mListener.onDisplayPortLinkTrainingFailure();
+ return;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
new file mode 100644
index 000000000000..5cdef38cd45c
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -0,0 +1,205 @@
+/*
+ * 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.display.notifications;
+
+import static android.app.Notification.COLOR_DEFAULT;
+import static com.android.internal.notification.SystemNotificationChannels.ALERTS;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Manages notifications for {@link com.android.server.display.DisplayManagerService}.
+ */
+public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener {
+ /** Dependency injection interface for {@link DisplayNotificationManager} */
+ public interface Injector {
+ /** Get {@link NotificationManager} service or null if not available. */
+ @Nullable
+ NotificationManager getNotificationManager();
+
+ /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */
+ @Nullable
+ ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector();
+ }
+
+ private static final String TAG = "DisplayNotificationManager";
+ private static final String NOTIFICATION_GROUP_NAME = TAG;
+ private static final String DISPLAY_NOTIFICATION_TAG = TAG;
+ private static final int DISPLAY_NOTIFICATION_ID = 1;
+ private static final long NOTIFICATION_TIMEOUT_MILLISEC = 30000L;
+
+ private final Injector mInjector;
+ private final Context mContext;
+ private final boolean mConnectedDisplayErrorHandlingEnabled;
+ private NotificationManager mNotificationManager;
+ private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector;
+
+ public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) {
+ this(flags, context, new Injector() {
+ @Nullable
+ @Override
+ public NotificationManager getNotificationManager() {
+ return context.getSystemService(NotificationManager.class);
+ }
+
+ @Nullable
+ @Override
+ public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() {
+ return new ConnectedDisplayUsbErrorsDetector(flags, context);
+ }
+ });
+ }
+
+ @VisibleForTesting
+ DisplayNotificationManager(final DisplayManagerFlags flags, final Context context,
+ final Injector injector) {
+ mConnectedDisplayErrorHandlingEnabled = flags.isConnectedDisplayErrorHandlingEnabled();
+ mContext = context;
+ mInjector = injector;
+ }
+
+ /**
+ * Initialize services, which may be not yet published during boot.
+ * see {@link android.os.ServiceManager.ServiceNotFoundException}.
+ */
+ public void onBootCompleted() {
+ mNotificationManager = mInjector.getNotificationManager();
+ if (mNotificationManager == null) {
+ Slog.e(TAG, "onBootCompleted: NotificationManager is null");
+ return;
+ }
+
+ mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector();
+ if (mConnectedDisplayUsbErrorsDetector != null) {
+ mConnectedDisplayUsbErrorsDetector.registerListener(this);
+ }
+ }
+
+ /**
+ * Display error notification upon DisplayPort link training failure.
+ */
+ @Override
+ public void onDisplayPortLinkTrainingFailure() {
+ if (!mConnectedDisplayErrorHandlingEnabled) {
+ Slog.d(TAG, "onDisplayPortLinkTrainingFailure:"
+ + " mConnectedDisplayErrorHandlingEnabled is false");
+ return;
+ }
+
+ sendErrorNotification(createErrorNotification(
+ R.string.connected_display_unavailable_notification_title,
+ R.string.connected_display_unavailable_notification_content));
+ }
+
+ /**
+ * Display error notification upon cable not capable of DisplayPort connected to a device
+ * capable of DisplayPort.
+ */
+ @Override
+ public void onCableNotCapableDisplayPort() {
+ if (!mConnectedDisplayErrorHandlingEnabled) {
+ Slog.d(TAG, "onCableNotCapableDisplayPort:"
+ + " mConnectedDisplayErrorHandlingEnabled is false");
+ return;
+ }
+
+ sendErrorNotification(createErrorNotification(
+ R.string.connected_display_cable_dont_support_displays_notification_title,
+ R.string.connected_display_cable_dont_support_displays_notification_content));
+ }
+
+ /**
+ * Send notification about hotplug connection error.
+ */
+ public void onHotplugConnectionError() {
+ if (!mConnectedDisplayErrorHandlingEnabled) {
+ Slog.d(TAG, "onHotplugConnectionError:"
+ + " mConnectedDisplayErrorHandlingEnabled is false");
+ return;
+ }
+
+ sendErrorNotification(createErrorNotification(
+ R.string.connected_display_unavailable_notification_title,
+ R.string.connected_display_unavailable_notification_content));
+ }
+
+ /**
+ * Cancel sent notifications.
+ */
+ public void cancelNotifications() {
+ if (mNotificationManager == null) {
+ Slog.e(TAG, "Can't cancelNotifications: NotificationManager is null");
+ return;
+ }
+
+ mNotificationManager.cancel(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID);
+ }
+
+ /**
+ * Send generic error notification.
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private void sendErrorNotification(final Notification notification) {
+ if (mNotificationManager == null) {
+ Slog.e(TAG, "Can't sendErrorNotification: NotificationManager is null");
+ return;
+ }
+
+ mNotificationManager.notify(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID,
+ notification);
+ }
+
+ /**
+ * @return a newly built notification about an issue with connected display.
+ */
+ private Notification createErrorNotification(final int titleId, final int messageId) {
+ final Resources resources = mContext.getResources();
+ final CharSequence title = resources.getText(titleId);
+ final CharSequence message = resources.getText(messageId);
+
+ int color = COLOR_DEFAULT;
+ try (var attrs = mContext.obtainStyledAttributes(new int[]{R.attr.colorError})) {
+ color = attrs.getColor(0, color);
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "colorError attribute is not found: " + e.getMessage());
+ }
+
+ return new Notification.Builder(mContext, ALERTS)
+ .setGroup(NOTIFICATION_GROUP_NAME)
+ .setSmallIcon(R.drawable.usb_cable_unknown_issue)
+ .setWhen(0)
+ .setTimeoutAfter(NOTIFICATION_TIMEOUT_MILLISEC)
+ .setOngoing(false)
+ .setTicker(title)
+ .setColor(color)
+ .setContentTitle(title)
+ .setContentText(message)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setCategory(Notification.CATEGORY_ERROR)
+ .build();
+ }
+}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d16a81267370..d804e01aa31e 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -444,7 +444,7 @@ public class PackageInfoUtils {
updateApplicationInfo(info, flags, state);
- initForUser(info, pkg, userId);
+ initForUser(info, pkg, userId, state);
// TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
PackageStateUnserialized pkgState = pkgSetting.getTransientState();
@@ -690,7 +690,7 @@ public class PackageInfoUtils {
info.splitDependencies = pkg.getSplitDependencies().size() == 0
? null : pkg.getSplitDependencies();
- initForUser(info, pkg, userId);
+ initForUser(info, pkg, userId, state);
info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
@@ -1006,7 +1006,7 @@ public class PackageInfoUtils {
}
private static void initForUser(ApplicationInfo output, AndroidPackage input,
- @UserIdInt int userId) {
+ @UserIdInt int userId, PackageUserStateInternal state) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
@@ -1016,6 +1016,13 @@ public class PackageInfoUtils {
return;
}
+ if (android.content.pm.Flags.nullableDataDir()
+ && !state.isInstalled() && !state.dataExists()) {
+ // The data dir has been deleted
+ output.dataDir = null;
+ return;
+ }
+
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
@@ -1050,7 +1057,7 @@ public class PackageInfoUtils {
// This duplicates the ApplicationInfo variant because it uses field assignment and the classes
// don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
private static void initForUser(InstrumentationInfo output, AndroidPackage input,
- @UserIdInt int userId) {
+ @UserIdInt int userId, PackageUserStateInternal state) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
if ("android".equals(packageName)) {
@@ -1058,6 +1065,13 @@ public class PackageInfoUtils {
return;
}
+ if (android.content.pm.Flags.nullableDataDir()
+ && !state.isInstalled() && !state.dataExists()) {
+ // The data dir has been deleted
+ output.dataDir = null;
+ return;
+ }
+
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
@@ -1089,12 +1103,23 @@ public class PackageInfoUtils {
}
}
- @NonNull
+ /**
+ * Returns the data dir of the app for the target user. Return null if the app isn't installed
+ * on the target user and doesn't have a data dir on the target user.
+ */
+ @Nullable
public static File getDataDir(PackageStateInternal ps, int userId) {
if ("android".equals(ps.getPackageName())) {
return Environment.getDataSystemDirectory();
}
+ if (android.content.pm.Flags.nullableDataDir()
+ && !ps.getUserStateOrDefault(userId).isInstalled()
+ && !ps.getUserStateOrDefault(userId).dataExists()) {
+ // The app has been uninstalled for the user and the data dir has been deleted
+ return null;
+ }
+
if (ps.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId,
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index 0275c7df4648..6e4069fbe4bd 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -40,6 +40,7 @@ android_test {
"services.core",
"servicestests-utils",
"testables",
+ "TestParameterInjector",
],
defaults: [
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2396905aecbf..d021f1d5aaea 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -114,17 +114,18 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayManagerService.DeviceStateListener;
import com.android.server.display.DisplayManagerService.SyncRoot;
import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.input.InputManagerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import com.google.common.truth.Expect;
+
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-import com.google.common.truth.Expect;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -201,9 +202,12 @@ public class DisplayManagerServiceTest {
@Override
LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
Handler handler, DisplayAdapter.Listener displayAdapterListener,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags,
+ DisplayNotificationManager displayNotificationManager) {
return new LocalDisplayAdapter(syncRoot, context, handler,
- displayAdapterListener, flags, new LocalDisplayAdapter.Injector() {
+ displayAdapterListener, flags,
+ mMockedDisplayNotificationManager,
+ new LocalDisplayAdapter.Injector() {
@Override
public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
return mSurfaceControlProxy;
@@ -248,13 +252,15 @@ public class DisplayManagerServiceTest {
@Override
LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
Handler handler, DisplayAdapter.Listener displayAdapterListener,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags,
+ DisplayNotificationManager displayNotificationManager) {
return new LocalDisplayAdapter(
syncRoot,
context,
handler,
displayAdapterListener,
flags,
+ mMockedDisplayNotificationManager,
new LocalDisplayAdapter.Injector() {
@Override
public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
@@ -288,6 +294,7 @@ public class DisplayManagerServiceTest {
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+ @Mock DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock IMediaProjectionManager mMockProjectionService;
@Mock IVirtualDeviceManager mIVirtualDeviceManager;
@Mock InputManagerInternal mMockInputManagerInternal;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 147e8f22aab6..9ac00624b343 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -60,6 +60,7 @@ import com.android.server.LocalServices;
import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
@@ -113,6 +114,8 @@ public class LocalDisplayAdapterTest {
@Mock
private LogicalLight mMockedBacklight;
@Mock
+ private DisplayNotificationManager mMockedDisplayNotificationManager;
+ @Mock
private DisplayManagerFlags mFlags;
private Handler mHandler;
@@ -148,7 +151,7 @@ public class LocalDisplayAdapterTest {
mInjector = new Injector();
when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
- mListener, mFlags, mInjector);
+ mListener, mFlags, mMockedDisplayNotificationManager, mInjector);
spyOn(mAdapter);
doReturn(mMockedContext).when(mAdapter).getOverlayContext();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
new file mode 100644
index 000000000000..d5a92cbc927f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.Injector;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ConnectedDisplayUsbErrorsDetector}
+ * Run: atest ConnectedDisplayUsbErrorsDetectorTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class ConnectedDisplayUsbErrorsDetectorTest {
+ @Mock
+ private Injector mMockedInjector;
+ @Mock
+ private UsbManager mMockedUsbManager;
+ @Mock
+ private DisplayManagerFlags mMockedFlags;
+ @Mock
+ private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener;
+
+ /** Setup tests. */
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNoErrorTypes(
+ @TestParameter final boolean isUsbManagerAvailable,
+ @TestParameter final boolean isUsbErrorsNotificationEnabled) {
+ // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure
+ assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled);
+ var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled);
+ // None of these should trigger an error now.
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+ verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+ verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+ }
+
+ @Test
+ public void testErrorOnUsbCableNotCapableDp() {
+ var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+ /*isUsbErrorsNotificationEnabled=*/ true);
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+ verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener).onCableNotCapableDisplayPort();
+ verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+ }
+
+ @Test
+ public void testErrorOnDpLinkTrainingFailure() {
+ var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+ /*isUsbErrorsNotificationEnabled=*/ true);
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+ verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+ verify(mMockedListener).onDisplayPortLinkTrainingFailure();
+ }
+
+ private ConnectedDisplayUsbErrorsDetector createErrorsDetector(
+ final boolean isUsbManagerAvailable,
+ final boolean isConnectedDisplayUsbErrorsNotificationEnabled) {
+ when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled())
+ .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled);
+ when(mMockedInjector.getUsbManager()).thenReturn(
+ (isUsbManagerAvailable) ? mMockedUsbManager : null);
+ var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags,
+ ApplicationProvider.getApplicationContext(), mMockedInjector);
+ detector.registerListener(mMockedListener);
+ return detector;
+ }
+
+ private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() {
+ return new DisplayPortAltModeInfo(
+ DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+ DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0);
+ }
+
+ private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() {
+ return new DisplayPortAltModeInfo(
+ DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+ DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false,
+ LINK_TRAINING_STATUS_FAILURE);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
new file mode 100644
index 000000000000..1d2034be4acb
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.display.notifications;
+
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.DisplayNotificationManager.Injector;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link DisplayNotificationManager}
+ * Run: atest DisplayNotificationManagerTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class DisplayNotificationManagerTest {
+ @Mock
+ private Injector mMockedInjector;
+ @Mock
+ private NotificationManager mMockedNotificationManager;
+ @Mock
+ private DisplayManagerFlags mMockedFlags;
+ @Captor
+ private ArgumentCaptor<String> mNotifyTagCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mNotifyNoteIdCaptor;
+ @Captor
+ private ArgumentCaptor<Notification> mNotifyAsUserNotificationCaptor;
+
+ /** Setup tests. */
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNotificationOnHotplugConnectionError() {
+ var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+ /*isErrorHandlingEnabled=*/ true);
+ dnm.onHotplugConnectionError();
+ assertExpectedNotification();
+ }
+
+ @Test
+ public void testNotificationOnDisplayPortLinkTrainingFailure() {
+ var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+ /*isErrorHandlingEnabled=*/ true);
+ dnm.onDisplayPortLinkTrainingFailure();
+ assertExpectedNotification();
+ }
+
+ @Test
+ public void testNotificationOnCableNotCapableDisplayPort() {
+ var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+ /*isErrorHandlingEnabled=*/ true);
+ dnm.onCableNotCapableDisplayPort();
+ assertExpectedNotification();
+ }
+
+ @Test
+ public void testNoErrorNotification(
+ @TestParameter final boolean isNotificationManagerAvailable,
+ @TestParameter final boolean isErrorHandlingEnabled) {
+ /* This case is tested by #testNotificationOnHotplugConnectionError,
+ #testNotificationOnDisplayPortLinkTrainingFailure,
+ #testNotificationOnCableNotCapableDisplayPort */
+ assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled);
+ var dnm = createDisplayNotificationManager(isNotificationManagerAvailable,
+ isErrorHandlingEnabled);
+ // None of these methods should trigger a notification now.
+ dnm.onHotplugConnectionError();
+ dnm.onDisplayPortLinkTrainingFailure();
+ dnm.onCableNotCapableDisplayPort();
+ verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any());
+ }
+
+ private DisplayNotificationManager createDisplayNotificationManager(
+ final boolean isNotificationManagerAvailable,
+ final boolean isErrorHandlingEnabled) {
+ when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(
+ isErrorHandlingEnabled);
+ when(mMockedInjector.getNotificationManager()).thenReturn(
+ (isNotificationManagerAvailable) ? mMockedNotificationManager : null);
+ // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest
+ when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ null);
+ final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags,
+ ApplicationProvider.getApplicationContext(), mMockedInjector);
+ displayNotificationManager.onBootCompleted();
+ return displayNotificationManager;
+ }
+
+ private void assertExpectedNotification() {
+ verify(mMockedNotificationManager).notify(
+ mNotifyTagCaptor.capture(),
+ mNotifyNoteIdCaptor.capture(),
+ mNotifyAsUserNotificationCaptor.capture());
+ assertThat(mNotifyTagCaptor.getValue()).isEqualTo("DisplayNotificationManager");
+ assertThat((int) mNotifyNoteIdCaptor.getValue()).isEqualTo(1);
+ final var notification = mNotifyAsUserNotificationCaptor.getValue();
+ assertThat(notification.getChannelId()).isEqualTo("ALERTS");
+ assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR);
+ assertThat(notification.visibility).isEqualTo(Notification.VISIBILITY_PUBLIC);
+ assertThat(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(0);
+ assertThat(notification.when).isEqualTo(0);
+ assertThat(notification.getTimeoutAfter()).isEqualTo(30000L);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
index 70527ce2ad32..44d676052352 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
@@ -238,7 +238,7 @@ public class AnrTimerTest {
}
@Override
- Handler getHandler(Handler.Callback callback) {
+ Handler newHandler(Handler.Callback callback) {
if (mTestHandler == null) {
mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
}
@@ -250,14 +250,18 @@ public class AnrTimerTest {
return mTestHandler;
}
+ /**
+ * This override returns the tracker supplied in the constructor. It does not create a
+ * new one.
+ */
@Override
- AnrTimer.CpuTracker getTracker() {
+ AnrTimer.CpuTracker newTracker() {
return mTracker;
}
/** For test purposes, always enable the feature. */
@Override
- boolean getFeatureEnabled() {
+ boolean isFeatureEnabled() {
return true;
}
}
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 0230d77e8e14..e3e708ec856d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -47,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -64,6 +65,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -1751,6 +1753,45 @@ public class BiometricServiceTest {
verifyNoMoreInteractions(callback);
}
+ @Test
+ public void testRegisterBiometricPromptOnKeyguardCallback_authenticationAlreadyStarted()
+ throws Exception {
+ final IBiometricPromptStatusListener callback =
+ mock(IBiometricPromptStatusListener.class);
+
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ true /* requireConfirmation */, null /* authenticators */);
+ mBiometricService.mImpl.registerBiometricPromptStatusListener(callback);
+
+ verify(callback).onBiometricPromptShowing();
+ }
+
+ @Test
+ public void testRegisterBiometricPromptOnKeyguardCallback_startAuth_dismissDialog()
+ throws Exception {
+ final IBiometricPromptStatusListener listener =
+ mock(IBiometricPromptStatusListener.class);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ mBiometricService.mImpl.registerBiometricPromptStatusListener(listener);
+ waitForIdle();
+
+ verify(listener).onBiometricPromptIdle();
+
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ true /* requireConfirmation */, null /* authenticators */);
+ waitForIdle();
+
+ verify(listener).onBiometricPromptShowing();
+
+ final byte[] hat = generateRandomHAT();
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, hat);
+ waitForIdle();
+
+ verify(listener, times(2)).onBiometricPromptIdle();
+ }
+
// Helper methods
private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
index 56ab755af47b..7e43566d56f8 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
@@ -37,6 +37,8 @@ class ColorModeControls : LinearLayout, WindowObserver {
private var window: Window? = null
private var currentModeDisplay: TextView? = null
+ private var desiredRatio = 0.0f
+
override fun onFinishInflate() {
super.onFinishInflate()
val window = window ?: throw IllegalStateException("Failed to attach window")
@@ -67,6 +69,7 @@ class ColorModeControls : LinearLayout, WindowObserver {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
+ desiredRatio = window?.desiredHdrHeadroom ?: 0.0f
val hdrVis = if (display.isHdrSdrRatioAvailable) {
display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener)
View.VISIBLE
@@ -83,6 +86,11 @@ class ColorModeControls : LinearLayout, WindowObserver {
}
private fun setColorMode(newMode: Int) {
+ if (newMode == ActivityInfo.COLOR_MODE_HDR &&
+ window!!.colorMode == ActivityInfo.COLOR_MODE_HDR) {
+ desiredRatio = (desiredRatio + 1) % 5.0f
+ window!!.desiredHdrHeadroom = desiredRatio
+ }
window!!.colorMode = newMode
updateModeInfoDisplay()
}