Merge "Camera Extensions: Add Get API to Framework" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index c4a1cb3..d7a99d3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6880,6 +6880,7 @@
method public CharSequence getName();
method @Nullable public String getParentChannelId();
method public android.net.Uri getSound();
+ method @FlaggedApi("android.app.notification_channel_vibration_effect_api") @Nullable public android.os.VibrationEffect getVibrationEffect();
method public long[] getVibrationPattern();
method public boolean hasUserSetImportance();
method public boolean hasUserSetSound();
@@ -6899,6 +6900,7 @@
method public void setName(CharSequence);
method public void setShowBadge(boolean);
method public void setSound(android.net.Uri, android.media.AudioAttributes);
+ method @FlaggedApi("android.app.notification_channel_vibration_effect_api") public void setVibrationEffect(@Nullable android.os.VibrationEffect);
method public void setVibrationPattern(long[]);
method public boolean shouldShowLights();
method public boolean shouldVibrate();
@@ -13839,6 +13841,7 @@
method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method @FlaggedApi("android.content.res.register_resource_paths") public static void registerResourcePaths(@NonNull String, @NonNull android.content.pm.ApplicationInfo);
method public void removeLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
field @AnyRes public static final int ID_NULL = 0; // 0x0
@@ -20268,6 +20271,7 @@
method @Nullable public android.hardware.input.HostUsiVersion getHostUsiVersion(@NonNull android.view.Display);
method @Nullable public android.view.InputDevice getInputDevice(int);
method public int[] getInputDeviceIds();
+ method @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") @Nullable public android.view.InputDevice.ViewBehavior getInputDeviceViewBehavior(int);
method @FloatRange(from=0, to=1) public float getMaximumObscuringOpacityForTouch();
method public boolean isStylusPointerIconEnabled();
method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
@@ -27320,6 +27324,7 @@
field public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = 18; // 0x12
field public static final int VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 6; // 0x6
field public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 5; // 0x5
+ field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int VIDEO_UNAVAILABLE_REASON_STOPPED = 19; // 0x13
field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1
field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
field public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2; // 0x2
@@ -27411,6 +27416,7 @@
method public void onRemoveBroadcastInfo(int);
method public void onRequestAd(@NonNull android.media.tv.AdRequest);
method public void onRequestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onResumePlayback();
method public boolean onSelectAudioPresentation(int, int);
method public boolean onSelectTrack(int, @Nullable String);
method public abstract void onSetCaptionEnabled(boolean);
@@ -27418,6 +27424,7 @@
method public abstract void onSetStreamVolume(@FloatRange(from=0.0, to=1.0) float);
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
method public void onSetTvMessageEnabled(int, boolean);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onStopPlayback(int);
method public void onSurfaceChanged(int, int, int);
method public long onTimeShiftGetCurrentPosition();
method public long onTimeShiftGetStartPosition();
@@ -27552,6 +27559,7 @@
method public boolean onUnhandledInputEvent(android.view.InputEvent);
method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
method public void reset();
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void resumePlayback();
method public void selectAudioPresentation(int, int);
method public void selectTrack(int, String);
method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -27564,6 +27572,7 @@
method public void setTvMessageEnabled(int, boolean);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void stopPlayback(int);
method public void timeShiftPause();
method public void timeShiftPlay(String, android.net.Uri);
method public void timeShiftResume();
@@ -27613,6 +27622,59 @@
package android.media.tv.ad {
@FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
+ method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
+ }
+
+ public abstract static class TvAdManager.TvAdServiceCallback {
+ ctor public TvAdManager.TvAdServiceCallback();
+ method public void onAdServiceAdded(@NonNull String);
+ method public void onAdServiceRemoved(@NonNull String);
+ method public void onAdServiceUpdated(@NonNull String);
+ }
+
+ @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public abstract class TvAdService extends android.app.Service {
+ ctor public TvAdService();
+ method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @Nullable public abstract android.media.tv.ad.TvAdService.Session onCreateSession(@NonNull String, @NonNull String);
+ field public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
+ field public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
+ }
+
+ public abstract static class TvAdService.Session implements android.view.KeyEvent.Callback {
+ ctor public TvAdService.Session(@NonNull android.content.Context);
+ method @CallSuper public void layoutSurface(int, int, int, int);
+ method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
+ method public boolean onKeyDown(int, @Nullable android.view.KeyEvent);
+ method public boolean onKeyLongPress(int, @Nullable android.view.KeyEvent);
+ method public boolean onKeyMultiple(int, int, @Nullable android.view.KeyEvent);
+ method public boolean onKeyUp(int, @Nullable android.view.KeyEvent);
+ method public abstract void onRelease();
+ method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+ method public void onSurfaceChanged(int, int, int);
+ method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
+ method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
+ }
+
+ @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public final class TvAdServiceInfo implements android.os.Parcelable {
+ ctor public TvAdServiceInfo(@NonNull android.content.Context, @NonNull android.content.ComponentName);
+ method public int describeContents();
+ method @NonNull public String getId();
+ method @Nullable public android.content.pm.ServiceInfo getServiceInfo();
+ method @NonNull public java.util.List<java.lang.String> getSupportedTypes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.ad.TvAdServiceInfo> CREATOR;
+ }
+
+ @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdView extends android.view.ViewGroup {
+ ctor public TvAdView(@NonNull android.content.Context);
+ ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
+ ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void onLayout(boolean, int, int, int, int);
+ method public void onMeasure(int, int);
+ method public void onVisibilityChanged(@NonNull android.view.View, int);
+ method public void prepareAdService(@NonNull String, @NonNull String);
}
}
@@ -27750,6 +27812,7 @@
method public void onRecordingTuned(@NonNull String, @NonNull android.net.Uri);
method public abstract void onRelease();
method public void onResetInteractiveApp();
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onSelectedTrackInfo(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
method public void onSetTeletextAppEnabled(boolean);
method public void onSignalStrength(int);
@@ -27784,6 +27847,7 @@
method @CallSuper public void requestCurrentVideoBounds();
method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSelectedTrackInfo();
method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
method @CallSuper public void requestStopRecording(@NonNull String);
@@ -27848,6 +27912,7 @@
method public void sendCurrentChannelUri(@Nullable android.net.Uri);
method public void sendCurrentTvInputId(@Nullable String);
method public void sendCurrentVideoBounds(@NonNull android.graphics.Rect);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendSelectedTrackInfo(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
method public void sendSigningResult(@NonNull String, @NonNull byte[]);
method public void sendStreamVolume(float);
method public void sendTimeShiftMode(int);
@@ -27883,6 +27948,7 @@
method public void onRequestCurrentVideoBounds(@NonNull String);
method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSelectedTrackInfo(@NonNull String);
method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
method public void onRequestStopRecording(@NonNull String, @NonNull String);
@@ -50439,6 +50505,10 @@
method public boolean isFromSource(int);
}
+ @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") public static final class InputDevice.ViewBehavior {
+ method @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") public boolean shouldSmoothScroll(int, int);
+ }
+
public abstract class InputEvent implements android.os.Parcelable {
method public int describeContents();
method public final android.view.InputDevice getDevice();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index d331455..37be5c7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -132,6 +132,11 @@
field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
}
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public class SignedPackage {
+ method @NonNull public byte[] getCertificateDigest();
+ method @NonNull public String getPkgName();
+ }
+
}
package android.hardware.usb {
@@ -428,6 +433,8 @@
public class SystemConfigManager {
method @NonNull public java.util.List<android.content.ComponentName> getEnabledComponentOverrides(@NonNull String);
+ method @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public java.util.Set<android.content.pm.SignedPackage> getEnhancedConfirmationTrustedInstallers();
+ method @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public java.util.Set<android.content.pm.SignedPackage> getEnhancedConfirmationTrustedPackages();
}
public final class Trace {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7763d84..ca45be8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13450,6 +13450,7 @@
public abstract class Connection extends android.telecom.Conferenceable {
method @Deprecated public final android.telecom.AudioState getAudioState();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public final int getCallDirection();
method @IntRange(from=0) public final long getConnectTimeMillis();
method public final long getConnectionStartElapsedRealtimeMillis();
method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
@@ -13464,7 +13465,9 @@
method public void setTelecomCallId(@NonNull String);
field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
field public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 262144; // 0x40000
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EVENT_CALL_QUALITY_REPORT = "android.telecom.event.CALL_QUALITY_REPORT";
field public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE = "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_CALL_QUALITY_REPORT = "android.telecom.extra.CALL_QUALITY_REPORT";
field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE";
field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE";
field public static final String EXTRA_DISABLE_ADD_CALL = "android.telecom.extra.DISABLE_ADD_CALL";
@@ -13732,6 +13735,7 @@
method @Deprecated public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
@@ -15040,7 +15044,7 @@
@FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public class TelephonyRegistryManager {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
- method public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
}
public final class ThermalMitigationRequest implements android.os.Parcelable {
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 8b8576a..b5e5074 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -721,28 +721,28 @@
/**
* Returns whether the device is currently locked for the user.
* <p>
- * This returns the device locked state for the {@link Context}'s user. If this user is the
- * current user, then the device is considered "locked" when the lock screen is showing (i.e.
- * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with
- * swipe), and the user has a PIN, pattern, or password.
+ * This method returns the device locked state for the {@link Context}'s user. The device is
+ * considered to be locked for a user when the user's apps are currently inaccessible and some
+ * form of lock screen authentication is required to regain access to them. The lock screen
+ * authentication typically uses PIN, pattern, password, or biometric. Some devices may support
+ * additional methods, such as unlock using a paired smartwatch. "Swipe" does not count as
+ * authentication; if the lock screen is dismissible with swipe, for example due to the lock
+ * screen being set to Swipe or due to the device being kept unlocked by being near a trusted
+ * bluetooth device or in a trusted location, the device is considered unlocked.
+ * <div class="note">
* <p>
- * Note: the above definition implies that a user with no PIN, pattern, or password is never
- * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The
- * device PIN and SIM PIN are separate. Also, the user is not considered locked if face
- * authentication has just completed or a trust agent is keeping the device unlocked, since in
- * these cases the lock screen is dismissible with swipe.
+ * <b>Note:</b> In the case of multiple full users, each user can have their own lock screen
+ * authentication configured. The device-locked state may differ between different users. For
+ * example, the device may be unlocked for the current user, but locked for a non-current user
+ * if lock screen authentication would be required to access that user's apps after switching to
+ * that user.
* <p>
- * For a user that is not the current user but can be switched to (usually this means "another
- * full user"), and that has a PIN, pattern, or password, the device is always considered
- * locked.
- * <p>
- * For a profile with a unified challenge, the device locked state is the same as that of the
- * parent user.
- * <p>
- * For a profile with a separate challenge, the device becomes unlocked when the profile's PIN,
- * pattern, password, or biometric is verified. It becomes locked when the parent user becomes
- * locked, the screen turns off, the device reboots, the device policy controller locks the
- * profile, or the timeout set by the device policy controller expires.
+ * In the case of a profile, when the device goes to the main lock screen, up to two layers of
+ * authentication may be required to regain access to the profile's apps: one to unlock the main
+ * lock screen, and one to unlock the profile (when a separate profile challenge is required).
+ * For a profile, the device is considered to be locked as long as any challenge remains, either
+ * the parent user's challenge (when applicable) or the profile's challenge (when applicable).
+ * </div>
*
* @return {@code true} if the device is currently locked for the user
* @see #isKeyguardLocked()
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d705eeb..8883907 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5946,6 +5946,12 @@
// there is enough space to do so (and fall back to the left edge if not).
big.setInt(R.id.actions, "setCollapsibleIndentDimen",
R.dimen.call_notification_collapsible_indent);
+ if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "setting evenly divided mode on action list");
+ }
+ big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
+ }
}
big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
if (numActions > 0 && !p.mHideActions) {
@@ -6421,7 +6427,15 @@
// Remove full-length color spans and ensure text contrast with the button fill.
title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
}
- button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
+ final CharSequence label = ensureColorSpanContrast(title, p);
+ if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "new action layout enabled, gluing instead of setting text");
+ }
+ button.setCharSequence(R.id.action0, "glueLabel", label);
+ } else {
+ button.setTextViewText(R.id.action0, label);
+ }
int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
if (tombstone) {
@@ -6438,7 +6452,14 @@
button.setColorStateList(R.id.action0, "setButtonBackground",
ColorStateList.valueOf(buttonFillColor));
if (p.mCallStyleActions) {
- button.setImageViewIcon(R.id.action0, action.getIcon());
+ if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
+ }
+ button.setIcon(R.id.action0, "glueIcon", action.getIcon());
+ } else {
+ button.setImageViewIcon(R.id.action0, action.getIcon());
+ }
boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
button.setBoolean(R.id.action0, "setIsPriority", priority);
int minWidthDimen =
@@ -9565,6 +9586,15 @@
* </pre>
*/
public static class CallStyle extends Style {
+ /**
+ * @hide
+ */
+ public static final boolean USE_NEW_ACTION_LAYOUT = false;
+
+ /**
+ * @hide
+ */
+ public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
/**
* @hide
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 15d692a..ab5395e 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -15,6 +15,7 @@
*/
package android.app;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -30,6 +31,9 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.VibrationEffect;
+import android.os.vibrator.persistence.VibrationXmlParser;
+import android.os.vibrator.persistence.VibrationXmlSerializer;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
@@ -49,6 +53,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.Objects;
@@ -146,6 +152,7 @@
private static final String ATT_LIGHTS = "lights";
private static final String ATT_LIGHT_COLOR = "light_color";
private static final String ATT_VIBRATION = "vibration";
+ private static final String ATT_VIBRATION_EFFECT = "vibration_effect";
private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
private static final String ATT_SOUND = "sound";
private static final String ATT_USAGE = "usage";
@@ -253,7 +260,8 @@
private boolean mSoundRestored = false;
private boolean mLights;
private int mLightColor = DEFAULT_LIGHT_COLOR;
- private long[] mVibration;
+ private long[] mVibrationPattern;
+ private VibrationEffect mVibrationEffect;
// Bitwise representation of fields that have been changed by the user, preventing the app from
// making changes to these fields.
private int mUserLockedFields;
@@ -324,9 +332,13 @@
mSound = null;
}
mLights = in.readByte() != 0;
- mVibration = in.createLongArray();
- if (mVibration != null && mVibration.length > MAX_VIBRATION_LENGTH) {
- mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH);
+ mVibrationPattern = in.createLongArray();
+ if (mVibrationPattern != null && mVibrationPattern.length > MAX_VIBRATION_LENGTH) {
+ mVibrationPattern = Arrays.copyOf(mVibrationPattern, MAX_VIBRATION_LENGTH);
+ }
+ if (Flags.notificationChannelVibrationEffectApi()) {
+ mVibrationEffect =
+ in.readInt() != 0 ? VibrationEffect.CREATOR.createFromParcel(in) : null;
}
mUserLockedFields = in.readInt();
mUserVisibleTaskShown = in.readByte() != 0;
@@ -381,7 +393,15 @@
dest.writeByte((byte) 0);
}
dest.writeByte(mLights ? (byte) 1 : (byte) 0);
- dest.writeLongArray(mVibration);
+ dest.writeLongArray(mVibrationPattern);
+ if (Flags.notificationChannelVibrationEffectApi()) {
+ if (mVibrationEffect != null) {
+ dest.writeInt(1);
+ mVibrationEffect.writeToParcel(dest, /* flags= */ 0);
+ } else {
+ dest.writeInt(0);
+ }
+ }
dest.writeInt(mUserLockedFields);
dest.writeByte(mUserVisibleTaskShown ? (byte) 1 : (byte) 0);
dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
@@ -585,8 +605,8 @@
/**
* Sets the vibration pattern for notifications posted to this channel. If the provided
- * pattern is valid (non-null, non-empty), will enable vibration on this channel
- * (equivalent to calling {@link #enableVibration(boolean)} with {@code true}).
+ * pattern is valid (non-null, non-empty with at least 1 non-zero value), will enable vibration
+ * on this channel (equivalent to calling {@link #enableVibration(boolean)} with {@code true}).
* Otherwise, vibration will be disabled unless {@link #enableVibration(boolean)} is
* used with {@code true}, in which case the default vibration will be used.
*
@@ -595,7 +615,56 @@
*/
public void setVibrationPattern(long[] vibrationPattern) {
this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0;
- this.mVibration = vibrationPattern;
+ this.mVibrationPattern = vibrationPattern;
+ if (Flags.notificationChannelVibrationEffectApi()) {
+ try {
+ this.mVibrationEffect =
+ VibrationEffect.createWaveform(vibrationPattern, /* repeat= */ -1);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ this.mVibrationEffect = null;
+ }
+ }
+ }
+
+ /**
+ * Sets a {@link VibrationEffect} for notifications posted to this channel. If the
+ * provided effect is non-null, will enable vibration on this channel (equivalent
+ * to calling {@link #enableVibration(boolean)} with {@code true}). Otherwise
+ * vibration will be disabled unless {@link #enableVibration(boolean)} is used with
+ * {@code true}, in which case the default vibration will be used.
+ *
+ * <p>The effect passed here will be returned from {@link #getVibrationEffect()}.
+ * If the provided {@link VibrationEffect} is an equivalent to a wave-form
+ * vibration pattern, the equivalent wave-form pattern will be returned from
+ * {@link #getVibrationPattern()}.
+ *
+ * <p>Note that some {@link VibrationEffect}s may not be playable on some devices.
+ * In cases where such an effect is passed here, vibration will still be enabled
+ * for the channel, but the default vibration will be used. Nonetheless, the
+ * provided effect will be stored and be returned from {@link #getVibrationEffect}
+ * calls, and could be used by the same channel on a different device, for example,
+ * in cases the user backs up and restores to a device that does have the ability
+ * to play the effect, where that effect will be used instead of the default. To
+ * avoid such issues that could make the vibration behavior of your notification
+ * channel differ among different devices, it's recommended that you avoid
+ * vibration effect primitives, as the support for them differs widely among
+ * devices (read {@link VibrationEffect.Composition} for more on vibration
+ * primitives).
+ *
+ * <p>Only modifiable before the channel is submitted to
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+ *
+ * @see #getVibrationEffect()
+ * @see Vibrator#areEffectsSupported(int...)
+ * @see Vibrator#arePrimitivesSupported(int...)
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API)
+ public void setVibrationEffect(@Nullable VibrationEffect effect) {
+ this.mVibrationEnabled = effect != null;
+ this.mVibrationEffect = effect;
+ this.mVibrationPattern =
+ effect == null
+ ? null : effect.computeCreateWaveformOffOnTimingsOrNull();
}
/**
@@ -768,7 +837,35 @@
* vibration is not enabled ({@link #shouldVibrate()}).
*/
public long[] getVibrationPattern() {
- return mVibration;
+ return mVibrationPattern;
+ }
+
+ /**
+ * Returns the {@link VibrationEffect} for notifications posted to this channel.
+ * The returned effect is derived from either the effect provided in the
+ * {@link #setVibrationEffect(VibrationEffect)} method, or the equivalent vibration effect
+ * of the pattern set via the {@link #setVibrationPattern(long[])} method, based on setter
+ * method that was called last.
+ *
+ * The returned effect will be ignored in one of the following cases:
+ * <ul>
+ * <li> vibration is not enabled for the channel (i.e. {@link #shouldVibrate()}
+ * returns {@code false}).
+ * <li> the effect is not supported/playable by the device. In this case, if
+ * vibration is enabled for the channel, the default channel vibration will
+ * be used instead.
+ * </ul>
+ *
+ * @return the {@link VibrationEffect} set via {@link
+ * #setVibrationEffect(VibrationEffect)}, or the equivalent of the
+ * vibration set via {@link #setVibrationPattern(long[])}.
+ *
+ * @see VibrationEffect#createWaveform(long[], int)
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API)
+ @Nullable
+ public VibrationEffect getVibrationEffect() {
+ return mVibrationEffect;
}
/**
@@ -991,7 +1088,19 @@
enableLights(safeBool(parser, ATT_LIGHTS, false));
setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
+ // Set the pattern before the effect, so that we can properly handle cases where the pattern
+ // is null, but the effect is not null (i.e. for non-waveform VibrationEffects - the ones
+ // which cannot be represented as a vibration pattern).
setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
+ if (Flags.notificationChannelVibrationEffectApi()) {
+ VibrationEffect vibrationEffect = safeVibrationEffect(parser, ATT_VIBRATION_EFFECT);
+ if (vibrationEffect != null) {
+ // Restore the effect only if it is not null. This allows to avoid undoing a
+ // `setVibrationPattern` call above, if that was done with a non-null pattern
+ // (e.g. back up from a version that did not support `setVibrationEffect`).
+ setVibrationEffect(vibrationEffect);
+ }
+ }
enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
setDeleted(safeBool(parser, ATT_DELETED, false));
@@ -1180,6 +1289,9 @@
if (getVibrationPattern() != null) {
out.attribute(null, ATT_VIBRATION, longArrayToString(getVibrationPattern()));
}
+ if (getVibrationEffect() != null) {
+ out.attribute(null, ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));
+ }
if (getUserLockedFields() != 0) {
out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields());
}
@@ -1260,6 +1372,9 @@
record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isUserVisibleTaskShown()));
record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
+ if (getVibrationEffect() != null) {
+ record.put(ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));
+ }
record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
record.put(ATT_DELETED, Boolean.toString(isDeleted()));
record.put(ATT_DELETED_TIME_MS, Long.toString(getDeletedTimeMs()));
@@ -1287,6 +1402,30 @@
return val == null ? null : Uri.parse(val);
}
+ private static String vibrationToString(VibrationEffect effect) {
+ StringWriter writer = new StringWriter();
+ try {
+ VibrationXmlSerializer.serialize(
+ effect, writer, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to serialize vibration: " + effect, e);
+ }
+ return writer.toString();
+ }
+
+ private static VibrationEffect safeVibrationEffect(TypedXmlPullParser parser, String att) {
+ final String val = parser.getAttributeValue(null, att);
+ if (val != null) {
+ try {
+ return VibrationXmlParser.parseVibrationEffect(
+ new StringReader(val), VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read serialized vibration effect", e);
+ }
+ }
+ return null;
+ }
+
private static int safeInt(TypedXmlPullParser parser, String att, int defValue) {
return parser.getAttributeInt(null, att, defValue);
}
@@ -1361,7 +1500,8 @@
&& Objects.equals(getName(), that.getName())
&& Objects.equals(mDesc, that.mDesc)
&& Objects.equals(getSound(), that.getSound())
- && Arrays.equals(mVibration, that.mVibration)
+ && Arrays.equals(mVibrationPattern, that.mVibrationPattern)
+ && Objects.equals(getVibrationEffect(), that.getVibrationEffect())
&& Objects.equals(getGroup(), that.getGroup())
&& Objects.equals(getAudioAttributes(), that.getAudioAttributes())
&& mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
@@ -1379,9 +1519,9 @@
getUserLockedFields(), isUserVisibleTaskShown(),
mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles,
- mImportanceLockedDefaultApp, mOriginalImportance,
+ mImportanceLockedDefaultApp, mOriginalImportance, getVibrationEffect(),
mParentId, mConversationId, mDemoted, mImportantConvo);
- result = 31 * result + Arrays.hashCode(mVibration);
+ result = 31 * result + Arrays.hashCode(mVibrationPattern);
return result;
}
@@ -1413,7 +1553,9 @@
+ ", mSound=" + mSound
+ ", mLights=" + mLights
+ ", mLightColor=" + mLightColor
- + ", mVibration=" + Arrays.toString(mVibration)
+ + ", mVibrationPattern=" + Arrays.toString(mVibrationPattern)
+ + ", mVibrationEffect="
+ + (mVibrationEffect == null ? "null" : mVibrationEffect.toString())
+ ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
+ ", mUserVisibleTaskShown=" + mUserVisibleTaskShown
+ ", mVibrationEnabled=" + mVibrationEnabled
@@ -1448,8 +1590,8 @@
}
proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
- if (mVibration != null) {
- for (long v : mVibration) {
+ if (mVibrationPattern != null) {
+ for (long v : mVibrationPattern) {
proto.write(NotificationChannelProto.VIBRATION, v);
}
}
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index edf0a46..6a114f9 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -114,6 +114,22 @@
}
/**
+ * Remove all Messages from the Handler with the given code.
+ *
+ * This method intentionally avoids creating the Handler if it doesn't
+ * already exist.
+ */
+ private static void handlerRemoveMessages(int what) {
+ synchronized (sLock) {
+ if (sHandler == null) {
+ // Nothing to remove
+ return;
+ }
+ getHandler().removeMessages(what);
+ }
+ }
+
+ /**
* Add a finisher-runnable to wait for {@link #queue asynchronously processed work}.
*
* Used by SharedPreferences$Editor#startCommit().
@@ -156,17 +172,13 @@
long startTime = System.currentTimeMillis();
boolean hadMessages = false;
- Handler handler = getHandler();
-
synchronized (sLock) {
- if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
- // Delayed work will be processed at processPendingWork() below
- handler.removeMessages(QueuedWorkHandler.MSG_RUN);
-
- if (DEBUG) {
- hadMessages = true;
- Log.d(LOG_TAG, "waiting");
- }
+ if (DEBUG) {
+ hadMessages = getHandler().hasMessages(QueuedWorkHandler.MSG_RUN);
+ }
+ handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
+ if (DEBUG && hadMessages) {
+ Log.d(LOG_TAG, "waiting");
}
// We should not delay any work as this might delay the finishers
@@ -257,7 +269,7 @@
sWork = new LinkedList<>();
// Remove all msg-s as all work will be processed now
- getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
+ handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
}
if (work.size() > 0) {
diff --git a/core/java/android/app/contextualsearch/OWNERS b/core/java/android/app/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/core/java/android/app/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index a5d4a14..c40b23e 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -50,3 +50,10 @@
description: "Adds a new voicemail category for notifications"
bug: "322806700"
}
+
+flag {
+ name: "notification_channel_vibration_effect_api"
+ namespace: "systemui"
+ description: "This flag enables the API to allow setting VibrationEffect for NotificationChannels"
+ bug: "241732519"
+}
\ No newline at end of file
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index e9b94c9..87fb843 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -185,7 +185,7 @@
*
* @param context A Context object which should be some mock instance (like the
* instance of {@link android.test.mock.MockContext}).
- * @param readPermission The read permision you want this instance should have in the
+ * @param readPermission The read permission you want this instance should have in the
* test, which is available via {@link #getReadPermission()}.
* @param writePermission The write permission you want this instance should have
* in the test, which is available via {@link #getWritePermission()}.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 9253998..a126363 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -387,7 +387,7 @@
* {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
* <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in
* the arguments {@link Bundle}, the Content framework will attempt to
- * synthesize an QUERY_ARG_SQL* argument using the corresponding
+ * synthesize a QUERY_ARG_SQL* argument using the corresponding
* QUERY_ARG_SORT* values.
*/
public static final String QUERY_ARG_SORT_COLUMNS = "android:query-arg-sort-columns";
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 1f25fd0..451c0e5 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -80,7 +80,7 @@
long timeout);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
- void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+ void requestArchive(String packageName, String callerPackageName, int flags, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c4bf18d..5df23c0 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2362,8 +2362,8 @@
public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
throws PackageManager.NameNotFoundException {
try {
- mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
- new UserHandle(mUserId));
+ mInstaller.requestArchive(packageName, mInstallerPackageName, /*flags=*/ 0,
+ statusReceiver, new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(PackageManager.NameNotFoundException.class);
} catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8744eae..bc29f8b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4479,6 +4479,10 @@
* the Android Keystore backed by an isolated execution environment. The version indicates
* which features are implemented in the isolated execution environment:
* <ul>
+ * <li>300: Ability to include a second IMEI in the ID attestation record, see
+ * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+ * <li>200: Hardware support for Curve 25519 (including both Ed25519 signature generation and
+ * X25519 key agreement).
* <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
* for app-generated attestation keys (see {@link
* android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
@@ -4508,6 +4512,11 @@
* StrongBox</a>. If this feature has a version, the version number indicates which features are
* implemented in StrongBox:
* <ul>
+ * <li>300: Ability to include a second IMEI in the ID attestation record, see
+ * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+ * <li>200: No new features for StrongBox (the Android Keystore environment backed by an
+ * isolated execution environment has gained support for Curve 25519 in this version, but
+ * the implementation backed by a dedicated secure processor is not expected to implement it).
* <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
* for app-generated attestation keys (see {@link
* android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
diff --git a/core/java/android/content/pm/SignedPackage.java b/core/java/android/content/pm/SignedPackage.java
new file mode 100644
index 0000000..4d1b136
--- /dev/null
+++ b/core/java/android/content/pm/SignedPackage.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A data class representing a package and (SHA-256 hash of) a signing certificate.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+public class SignedPackage {
+ @NonNull
+ private final SignedPackageParcel mData;
+
+ /** @hide */
+ public SignedPackage(@NonNull String pkgName, @NonNull byte[] certificateDigest) {
+ SignedPackageParcel data = new SignedPackageParcel();
+ data.pkgName = pkgName;
+ data.certificateDigest = certificateDigest;
+ mData = data;
+ }
+
+ /** @hide */
+ public SignedPackage(@NonNull SignedPackageParcel data) {
+ mData = data;
+ }
+
+ /** @hide */
+ public final @NonNull SignedPackageParcel getData() {
+ return mData;
+ }
+
+ public @NonNull String getPkgName() {
+ return mData.pkgName;
+ }
+
+ public @NonNull byte[] getCertificateDigest() {
+ return mData.certificateDigest;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SignedPackage that)) return false;
+ return mData.pkgName.equals(that.mData.pkgName) && Arrays.equals(mData.certificateDigest,
+ that.mData.certificateDigest);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mData.pkgName, Arrays.hashCode(mData.certificateDigest));
+ }
+}
diff --git a/core/java/android/content/pm/SignedPackageParcel.aidl b/core/java/android/content/pm/SignedPackageParcel.aidl
new file mode 100644
index 0000000..7957f7f
--- /dev/null
+++ b/core/java/android/content/pm/SignedPackageParcel.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.content.pm;
+
+import android.content.ComponentName;
+
+/** @hide */
+parcelable SignedPackageParcel {
+ String pkgName;
+ byte[] certificateDigest;
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1b37092..3671980 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -29,6 +29,7 @@
import android.annotation.DimenRes;
import android.annotation.Discouraged;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.FontRes;
import android.annotation.FractionRes;
import android.annotation.IntegerRes;
@@ -46,6 +47,7 @@
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
+import android.content.pm.ApplicationInfo;
import android.content.res.loader.ResourcesLoader;
import android.graphics.Movie;
import android.graphics.Typeface;
@@ -2825,4 +2827,22 @@
}
}
}
+
+ /**
+ * Register the resources paths of a package (e.g. a shared library). This will collect the
+ * package resources' paths from its ApplicationInfo and add them to all existing and future
+ * contexts while the application is running.
+ * A second call with the same uniqueId is a no-op.
+ * The paths are not persisted during application restarts. The application is responsible for
+ * calling the API again if this happens.
+ *
+ * @param uniqueId The unique id for the ApplicationInfo object, to detect and ignore repeated
+ * API calls.
+ * @param appInfo The ApplicationInfo that contains resources paths of the package.
+ */
+ @FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public static void registerResourcePaths(@NonNull String uniqueId,
+ @NonNull ApplicationInfo appInfo) {
+ throw new UnsupportedOperationException("The implementation has not been done yet.");
+ }
}
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index db81e84..f660770 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -40,3 +40,12 @@
description: "Feature flag for creating an frro from a 9-patch"
bug: "309232726"
}
+
+flag {
+ name: "register_resource_paths"
+ namespace: "resource_manager"
+ description: "Feature flag for register resource paths for shared library"
+ bug: "306202569"
+ # This flag is read in ResourcesImpl at boot time.
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index c3a09ae..e8d5d37 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -20,6 +20,7 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.ComponentName;
@@ -49,7 +50,7 @@
public static Intent createCredentialSelectorIntent(
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
- @NonNull
+ @Nullable
ArrayList<ProviderData> enabledProviderDataList,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@NonNull
@@ -57,23 +58,30 @@
@NonNull ResultReceiver resultReceiver,
boolean isRequestForAllOptions) {
- Intent intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
- disabledProviderDataList, resultReceiver);
+ Intent intent;
+ if (enabledProviderDataList != null) {
+ intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
+ disabledProviderDataList, resultReceiver);
+ } else {
+ intent = createCredentialSelectorIntent(requestInfo,
+ disabledProviderDataList, resultReceiver);
+ }
intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
return intent;
}
- /** Generate a new launch intent to the Credential Selector UI. */
+ /**
+ * Generate a new launch intent to the Credential Selector UI.
+ *
+ * @hide
+ */
@NonNull
public static Intent createCredentialSelectorIntent(
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
- @NonNull
- ArrayList<ProviderData> enabledProviderDataList,
- @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
- @NonNull
- ArrayList<DisabledProviderData> disabledProviderDataList,
+ @NonNull
+ ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
Intent intent = new Intent();
ComponentName componentName =
@@ -83,9 +91,6 @@
com.android.internal.R.string
.config_credentialManagerDialogComponent));
intent.setComponent(componentName);
-
- intent.putParcelableArrayListExtra(
- ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
intent.putParcelableArrayListExtra(
ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -95,6 +100,24 @@
return intent;
}
+ /** Generate a new launch intent to the Credential Selector UI. */
+ @NonNull
+ public static Intent createCredentialSelectorIntent(
+ @NonNull RequestInfo requestInfo,
+ @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+ @NonNull
+ ArrayList<ProviderData> enabledProviderDataList,
+ @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+ @NonNull
+ ArrayList<DisabledProviderData> disabledProviderDataList,
+ @NonNull ResultReceiver resultReceiver) {
+ Intent intent = createCredentialSelectorIntent(requestInfo,
+ disabledProviderDataList, resultReceiver);
+ intent.putParcelableArrayListExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
+ return intent;
+ }
+
/**
* Creates an Intent that cancels any UI matching the given request token id.
*
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index cb1d3f5..3b7ade2 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -511,7 +511,7 @@
Bundle getExtras();
/**
- * This is an out-of-band way for the the user of a cursor to communicate with the cursor. The
+ * This is an out-of-band way for the user of a cursor to communicate with the cursor. The
* structure of each bundle is entirely defined by the cursor.
*
* <p>One use of this is to tell a cursor that it should retry its network request after it
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 665d8d2..451d6fb 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5313,7 +5313,7 @@
* </code></pre>
* <ul>
* <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
- * <li>AE_TARGET_FPS_RANGE: {{<em>, 30}, {</em>, 60}}</li>
+ * <li>AE_TARGET_FPS_RANGE: { {<em>, 30}, {</em>, 60} }</li>
* <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
* </ul>
* <p>This key is available on all devices.</p>
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 4ebbde7..db992cd 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,9 +16,11 @@
package android.hardware.input;
+import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -294,6 +296,23 @@
}
/**
+ * Gets the {@link InputDevice.ViewBehavior} of the input device with a given {@code id}.
+ *
+ * <p>Use this API to query a fresh view behavior instance whenever the input device
+ * changes.
+ *
+ * @param deviceId the id of the input device whose view behavior is being requested.
+ * @return the view behavior of the input device with the provided id, or {@code null} if there
+ * is not input device with the provided id.
+ */
+ @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+ @Nullable
+ public InputDevice.ViewBehavior getInputDeviceViewBehavior(int deviceId) {
+ InputDevice device = getInputDevice(deviceId);
+ return device == null ? null : device.getViewBehavior();
+ }
+
+ /**
* Gets information about the input device with the specified descriptor.
* @param descriptor The input device descriptor.
* @return The input device or null if not found.
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index b7649ba..650aead 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -17,6 +17,8 @@
package android.os;
import android.content.ComponentName;
+import android.os.Bundle;
+import android.content.pm.SignedPackageParcel;
/**
* Binder interface to query SystemConfig in the system server.
@@ -57,4 +59,14 @@
* @see SystemConfigManager#getPreventUserDisablePackages
*/
List<String> getPreventUserDisablePackages();
+
+ /**
+ * @see SystemConfigManager#getEnhancedConfirmationTrustedPackages
+ */
+ List<SignedPackageParcel> getEnhancedConfirmationTrustedPackages();
+
+ /**
+ * @see SystemConfigManager#getEnhancedConfirmationTrustedInstallers
+ */
+ List<SignedPackageParcel> getEnhancedConfirmationTrustedInstallers();
}
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 21ffbf1..13bc398 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -16,12 +16,15 @@
package android.os;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.SignedPackage;
+import android.content.pm.SignedPackageParcel;
import android.util.ArraySet;
import android.util.Log;
@@ -29,6 +32,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
/**
@@ -175,4 +179,69 @@
throw e.rethrowFromSystemServer();
}
}
+
+
+ /**
+ * Returns a set of signed packages, represented as (packageName, certificateDigest) pairs, that
+ * should be considered "trusted packages" by ECM (Enhanced Confirmation Mode).
+ *
+ * <p>"Trusted packages" are exempt from ECM (i.e., they will never be considered "restricted").
+ *
+ * <p>A package will be considered "trusted package" if and only if it *matches* least one of
+ * the (*packageName*, *certificateDigest*) pairs in this set, where *matches* means satisfying
+ * both of the following:
+ *
+ * <ol>
+ * <li>The package's name equals *packageName*
+ * <li>The package is, or was ever, signed by *certificateDigest*, according to the package's
+ * {@link android.content.pm.SigningDetails}
+ * </ol>
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @RequiresPermission(Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES)
+ @NonNull
+ public Set<SignedPackage> getEnhancedConfirmationTrustedPackages() {
+ try {
+ List<SignedPackageParcel> parcels = mInterface.getEnhancedConfirmationTrustedPackages();
+ return parcels.stream().map(SignedPackage::new).collect(Collectors.toSet());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a set of signed packages, represented as (packageName, certificateDigest) pairs, that
+ * should be considered "trusted installers" by ECM (Enhanced Confirmation Mode).
+ *
+ * <p>"Trusted installers", and all apps installed by a trusted installer, are exempt from ECM
+ * (i.e., they will never be considered "restricted").
+ *
+ * <p>A package will be considered a "trusted installer" if and only if it *matches* least one
+ * of the (*packageName*, *certificateDigest*) pairs in this set, where *matches* means
+ * satisfying both of the following:
+ *
+ * <ol>
+ * <li>The package's name equals *packageName*
+ * <li>The package is, or was ever, signed by *certificateDigest*, according to the package's
+ * {@link android.content.pm.SigningDetails}
+ * </ol>
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @RequiresPermission(Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES)
+ @NonNull
+ public Set<SignedPackage> getEnhancedConfirmationTrustedInstallers() {
+ try {
+ List<SignedPackageParcel> parcels =
+ mInterface.getEnhancedConfirmationTrustedInstallers();
+ return parcels.stream().map(SignedPackage::new).collect(Collectors.toSet());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index f3496e7..b1ef05a 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -71,7 +71,7 @@
*/
public class ZygoteProcess {
- private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
+ private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 60000;
/**
* Use a relatively short delay, because for app zygote, this is in the critical path of
diff --git a/core/java/android/service/contextualsearch/OWNERS b/core/java/android/service/contextualsearch/OWNERS
new file mode 100644
index 0000000..463adf4
--- /dev/null
+++ b/core/java/android/service/contextualsearch/OWNERS
@@ -0,0 +1,3 @@
+srazdan@google.com
+volnov@google.com
+hackz@google.com
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a813a3..d39c4ce 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -19,12 +19,15 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.SystemService;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
@@ -73,9 +76,15 @@
* Limit API access to only carrier apps with certain permissions or apps running on
* privileged UID.
*
+ * TelephonyRegistryManager is intended for use on devices that implement
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+ * that do not implement this feature, the behavior is not reliable.
+ *
* @hide
*/
@SystemApi
+@SystemService(Context.TELEPHONY_REGISTRY_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY)
@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public class TelephonyRegistryManager {
@@ -389,10 +398,11 @@
}
/**
- * Notify call state changed on all subscriptions.
+ * Notify call state changed on all subscriptions, excluding over-the-top VOIP calls (otherwise
+ * known as self-managed calls in the Android Platform).
*
* @param state latest call state. e.g, offhook, ringing
- * @param incomingNumber incoming phone number.
+ * @param incomingNumber incoming phone number or null in the case for OTT VOIP calls
* @hide
*/
@SystemApi
@@ -627,17 +637,20 @@
}
/**
- * Notify outgoing emergency call.
+ * Notify outgoing emergency call to all applications that have registered a listener
+ * ({@link PhoneStateListener}) or a callback ({@link TelephonyCallback}) to monitor changes in
+ * telephony states.
* @param simSlotIndex Sender phone ID.
- * @param subId Sender subscription ID.
+ * @param subscriptionId Sender subscription ID.
* @param emergencyNumber Emergency number.
* @hide
*/
@SystemApi
- public void notifyOutgoingEmergencyCall(int simSlotIndex, int subId,
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void notifyOutgoingEmergencyCall(int simSlotIndex, int subscriptionId,
@NonNull EmergencyNumber emergencyNumber) {
try {
- sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subId, emergencyNumber);
+ sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subscriptionId, emergencyNumber);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/tracing/perfetto/DataSourceInstance.java b/core/java/android/tracing/perfetto/DataSourceInstance.java
index 4994501..3710b4d 100644
--- a/core/java/android/tracing/perfetto/DataSourceInstance.java
+++ b/core/java/android/tracing/perfetto/DataSourceInstance.java
@@ -69,4 +69,8 @@
public final void release() {
mDataSource.releaseDataSourceInstance(mInstanceIndex);
}
+
+ public final int getInstanceIndex() {
+ return mInstanceIndex;
+ }
}
diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
index baece75..82559da 100644
--- a/core/java/android/tracing/transition/TransitionDataSource.java
+++ b/core/java/android/tracing/transition/TransitionDataSource.java
@@ -24,8 +24,8 @@
import android.tracing.perfetto.StopCallbackArguments;
import android.util.proto.ProtoInputStream;
-import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* @hide
@@ -38,6 +38,9 @@
private final Runnable mOnFlushStaticCallback;
private final Runnable mOnStopStaticCallback;
+ private final ConcurrentHashMap<Integer, ConcurrentHashMap<String, Integer>> mHandlerMappings =
+ new ConcurrentHashMap<>();
+
public TransitionDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
super(DATA_SOURCE_NAME);
this.mOnStartStaticCallback = onStart;
@@ -47,11 +50,16 @@
@Override
protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
- return new TlsState();
+ return new TlsState(args.getDataSourceInstanceLocked().getInstanceIndex());
}
public class TlsState {
- public final Map<String, Integer> handlerMapping = new HashMap<>();
+ public final Map<String, Integer> handlerMapping;
+
+ public TlsState(int instanceIndex) {
+ handlerMapping = mHandlerMappings
+ .computeIfAbsent(instanceIndex, index -> new ConcurrentHashMap<>());
+ }
}
@Override
@@ -70,6 +78,7 @@
@Override
protected void onStop(StopCallbackArguments args) {
mOnStopStaticCallback.run();
+ mHandlerMappings.remove(instanceIndex);
}
};
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f2c3abc..891e2a2 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,7 +16,10 @@
package android.view;
+import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
+
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +31,7 @@
import android.hardware.SensorManager;
import android.hardware.input.HostUsiVersion;
import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
import android.hardware.lights.LightsManager;
import android.icu.util.ULocale;
@@ -90,6 +94,8 @@
private final int mAssociatedDisplayId;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
+ private final ViewBehavior mViewBehavior = new ViewBehavior(this);
+
@GuardedBy("mMotionRanges")
private Vibrator mVibrator; // guarded by mMotionRanges during initialization
@@ -539,6 +545,8 @@
addMotionRange(in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
in.readFloat(), in.readFloat(), in.readFloat());
}
+
+ mViewBehavior.mShouldSmoothScroll = in.readBoolean();
}
/**
@@ -571,6 +579,7 @@
private int mUsiVersionMinor = -1;
private int mAssociatedDisplayId = Display.INVALID_DISPLAY;
private List<MotionRange> mMotionRanges = new ArrayList<>();
+ private boolean mShouldSmoothScroll;
/** @see InputDevice#getId() */
public Builder setId(int id) {
@@ -706,6 +715,16 @@
return this;
}
+ /**
+ * Sets the view behavior for smooth scrolling ({@code false} by default).
+ *
+ * @see ViewBehavior#shouldSmoothScroll(int, int)
+ */
+ public Builder setShouldSmoothScroll(boolean shouldSmoothScroll) {
+ mShouldSmoothScroll = shouldSmoothScroll;
+ return this;
+ }
+
/** Build {@link InputDevice}. */
public InputDevice build() {
InputDevice device = new InputDevice(
@@ -745,6 +764,8 @@
range.getResolution());
}
+ device.setShouldSmoothScroll(mShouldSmoothScroll);
+
return device;
}
}
@@ -1123,6 +1144,22 @@
return mMotionRanges;
}
+ /**
+ * Provides the {@link ViewBehavior} for the device.
+ *
+ * <p>This behavior is designed to be obtained using the
+ * {@link InputManager#getInputDeviceViewBehavior(int)} API, to allow associating the behavior
+ * with a {@link Context} (since input device is not associated with a context).
+ * The ability to associate the behavior with a context opens capabilities like linking the
+ * behavior to user settings, for example.
+ *
+ * @hide
+ */
+ @NonNull
+ public ViewBehavior getViewBehavior() {
+ return mViewBehavior;
+ }
+
// Called from native code.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void addMotionRange(int axis, int source,
@@ -1130,6 +1167,11 @@
mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz, resolution));
}
+ // Called from native code.
+ private void setShouldSmoothScroll(boolean shouldSmoothScroll) {
+ mViewBehavior.mShouldSmoothScroll = shouldSmoothScroll;
+ }
+
/**
* Returns the Bluetooth address of this input device, if known.
*
@@ -1447,6 +1489,82 @@
}
}
+ /**
+ * Provides information on how views processing {@link MotionEvent}s generated by this input
+ * device should respond to the events. Use {@link InputManager#getInputDeviceViewBehavior(int)}
+ * to get an instance of the view behavior for an input device.
+ *
+ * <p>See an example below how a {@link View} can use this class to determine and apply the
+ * scrolling behavior for a generic {@link MotionEvent}.
+ *
+ * <pre>{@code
+ * public boolean onGenericMotionEvent(MotionEvent event) {
+ * InputManager manager = context.getSystemService(InputManager.class);
+ * ViewBehavior viewBehavior = manager.getInputDeviceViewBehavior(event.getDeviceId());
+ * // Assume a helper function that tells us which axis to use for scrolling purpose.
+ * int axis = getScrollAxisForGenericMotionEvent(event);
+ * int source = event.getSource();
+ *
+ * boolean shouldSmoothScroll =
+ * viewBehavior != null && viewBehavior.shouldSmoothScroll(axis, source);
+ * // Proceed to running the scrolling logic...
+ * }
+ * }</pre>
+ *
+ * @see InputManager#getInputDeviceViewBehavior(int)
+ */
+ @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+ public static final class ViewBehavior {
+ private static final boolean DEFAULT_SHOULD_SMOOTH_SCROLL = false;
+
+ private final InputDevice mInputDevice;
+
+ // TODO(b/246946631): implement support for InputDevices to adjust this configuration
+ // by axis and source. When implemented, the axis/source specific config will take
+ // precedence over this global config.
+ /** A global smooth scroll configuration applying to all motion axis and input source. */
+ private boolean mShouldSmoothScroll = DEFAULT_SHOULD_SMOOTH_SCROLL;
+
+ /** @hide */
+ public ViewBehavior(@NonNull InputDevice inputDevice) {
+ mInputDevice = inputDevice;
+ }
+
+ /**
+ * Returns whether a view should smooth scroll when scrolling due to a {@link MotionEvent}
+ * generated by the input device.
+ *
+ * <p>Smooth scroll in this case refers to a scroll that animates the transition between
+ * the starting and ending positions of the scroll. When this method returns {@code true},
+ * views should try to animate a scroll generated by this device at the given axis and with
+ * the given source to produce a good scroll user experience. If this method returns
+ * {@code false}, animating scrolls is not necessary.
+ *
+ * <p>If the input device does not have a {@link MotionRange} with the provided axis and
+ * source, this method returns {@code false}.
+ *
+ * @param axis the {@link MotionEvent} axis whose value is used to get the scroll extent.
+ * @param source the {link InputDevice} source from which the {@link MotionEvent} that
+ * triggers the scroll came.
+ * @return {@code true} if smooth scrolling should be used for the scroll, or {@code false}
+ * if smooth scrolling is not necessary, or if the provided axis and source combination
+ * is not available for the input device.
+ */
+ @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+ public boolean shouldSmoothScroll(int axis, int source) {
+ // Note: although we currently do not use axis and source in computing the return value,
+ // we will keep the API params to avoid further public API changes when we start
+ // supporting axis/source configuration. Also, having these params lets OEMs provide
+ // their custom implementation of the API that depends on axis and source.
+
+ // TODO(b/246946631): speed up computation using caching of results.
+ if (mInputDevice.getMotionRange(axis, source) == null) {
+ return false;
+ }
+ return mShouldSmoothScroll;
+ }
+ }
+
@Override
public void writeToParcel(Parcel out, int flags) {
mKeyCharacterMap.writeToParcel(out, flags);
@@ -1484,6 +1602,8 @@
out.writeFloat(range.mFuzz);
out.writeFloat(range.mResolution);
}
+
+ out.writeBoolean(mViewBehavior.mShouldSmoothScroll);
}
@Override
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 59ec605..9db1060 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -162,6 +162,12 @@
public float alpha;
/**
+ * Sets a property on this window indicating that its visible region should be considered when
+ * computing TrustedPresentation Thresholds.
+ */
+ public boolean canOccludePresentation;
+
+ /**
* The input token for the window to which focus should be transferred when this input window
* can be successfully focused. If null, this input window will not transfer its focus to
* any other window.
@@ -205,6 +211,7 @@
focusTransferTarget = other.focusTransferTarget;
contentSize = new Size(other.contentSize.getWidth(), other.contentSize.getHeight());
alpha = other.alpha;
+ canOccludePresentation = other.canOccludePresentation;
}
@Override
@@ -219,6 +226,7 @@
.append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
.append(", contentSize=").append(contentSize)
.append(", alpha=").append(alpha)
+ .append(", canOccludePresentation=").append(canOccludePresentation)
.toString();
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3ed0385..3c0ac06 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -168,6 +168,8 @@
boolean isTrustedOverlay);
private static native void nativeSetDropInputMode(
long transactionObj, long nativeObject, int flags);
+ private static native void nativeSetCanOccludePresentation(long transactionObj,
+ long nativeObject, boolean canOccludePresentation);
private static native void nativeSurfaceFlushJankData(long nativeSurfaceObject);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -589,6 +591,28 @@
public static final int DISPLAY_DECORATION = 0x00000200;
/**
+ * Ignore any destination frame set on the layer. This is used when the buffer scaling mode
+ * is freeze and the destination frame is applied asynchronously with the buffer submission.
+ * This is needed to maintain compatibility for SurfaceView scaling behavior.
+ * See SurfaceView scaling behavior for more details.
+ * @hide
+ */
+ public static final int IGNORE_DESTINATION_FRAME = 0x00000400;
+
+ /**
+ * Special casing for layer that is a refresh rate indicator
+ * @hide
+ */
+ public static final int LAYER_IS_REFRESH_RATE_INDICATOR = 0x00000800;
+
+ /**
+ * Sets a property on this layer indicating that its visible region should be considered when
+ * computing TrustedPresentation Thresholds
+ * @hide
+ */
+ public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;
+
+ /**
* Surface creation flag: Creates a surface where color components are interpreted
* as "non pre-multiplied" by their alpha channel. Of course this flag is
* meaningless for surfaces without an alpha channel. By default
@@ -4163,6 +4187,29 @@
}
/**
+ * Sets a property on this SurfaceControl and all its children indicating that the visible
+ * region of this SurfaceControl should be considered when computing TrustedPresentation
+ * Thresholds.
+ * <p>
+ * API Guidance:
+ * The goal of this API is to identify windows that can be used to occlude content on
+ * another window. This includes windows controlled by the user or the system. If the window
+ * is transient, like Toast or notification shade, the window should not set this flag since
+ * the user or the app cannot use the window to occlude content in a persistent manner. All
+ * apps should have this flag set.
+ * <p>
+ * The caller must hold the ACCESS_SURFACE_FLINGER permission.
+ * @hide
+ */
+ public Transaction setCanOccludePresentation(SurfaceControl sc,
+ boolean canOccludePresentation) {
+ checkPreconditions(sc);
+ final int value = (canOccludePresentation) ? CAN_OCCLUDE_PRESENTATION : 0;
+ nativeSetFlags(mNativeObject, sc.mNativeObject, value, CAN_OCCLUDE_PRESENTATION);
+ return this;
+ }
+
+ /**
* Sends a flush jank data transaction for the given surface.
* @hide
*/
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1fdd1a5..f54ef38 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -425,12 +425,12 @@
int mMotionViewNewTop;
/**
- * The X value associated with the the down motion event
+ * The X value associated with the down motion event
*/
int mMotionX;
/**
- * The Y value associated with the the down motion event
+ * The Y value associated with the down motion event
*/
@UnsupportedAppUsage
int mMotionY;
@@ -7381,7 +7381,7 @@
scrap.dispatchStartTemporaryDetach();
- // The the accessibility state of the view may change while temporary
+ // the accessibility state of the view may change while temporary
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
@@ -7750,7 +7750,7 @@
}
/**
- * Abstract positon scroller used to handle smooth scrolling.
+ * Abstract position scroller used to handle smooth scrolling.
*/
static abstract class AbsPositionScroller {
public abstract void start(int position);
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 76e97ad..3b7e1e9 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -170,7 +170,7 @@
* @see android.view.View#measure(int, int)
*
* Figure out the dimensions of this Spinner. The width comes from
- * the widthMeasureSpec as Spinnners can't have their width set to
+ * the widthMeasureSpec as Spinners can't have their width set to
* UNSPECIFIED. The height is based on the height of the selected item
* plus padding.
*/
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index e20357fa..1dc90ed 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -15,3 +15,5 @@
per-file Remote* = file:../appwidget/OWNERS
per-file Toast.java = juliacr@google.com, jeffdq@google.com
+
+per-file flags/notification_widget_flags.aconfig = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index f234637..14fb17c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -84,6 +84,14 @@
}
flag {
+ name: "delegate_unhandled_drags"
+ namespace: "multitasking"
+ description: "Enables delegating unhandled drags to SystemUI"
+ bug: "320797628"
+ is_fixed_read_only: true
+}
+
+flag {
name: "insets_decoupled_configuration"
namespace: "windowing_frontend"
description: "Configuration decoupled from insets"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4a6e8d7..2e20cce 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -16,7 +16,7 @@
namespace: "windowing_sdk"
name: "activity_embedding_overlay_presentation_flag"
description: "Whether the overlay presentation feature is enabled"
- bug: "243518738"
+ bug: "293370683"
}
flag {
@@ -30,7 +30,7 @@
namespace: "windowing_sdk"
name: "fullscreen_dim_flag"
description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
- bug: "253533308"
+ bug: "293797706"
}
flag {
@@ -44,7 +44,7 @@
namespace: "windowing_sdk"
name: "untrusted_embedding_any_app_permission"
description: "Feature flag to enable the permission to embed any app in untrusted mode."
- bug: "289199433"
+ bug: "293647332"
is_fixed_read_only: true
}
@@ -60,5 +60,5 @@
namespace: "windowing_sdk"
name: "embedded_activity_back_nav_flag"
description: "Refines embedded activity back navigation behavior"
- bug: "240575809"
+ bug: "293642394"
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index ce6af49..5cda3f2 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,16 +16,30 @@
package com.android.internal.widget;
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.ImageSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.RemotableViewMethod;
import android.widget.Button;
import android.widget.RemoteViews;
@@ -43,6 +57,14 @@
private final GradientDrawable mBackground;
private boolean mPriority;
+ private int mInitialDrawablePadding;
+ private int mIconSize;
+
+ private Drawable mIconToGlue;
+ private CharSequence mLabelToGlue;
+ private int mGluedLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+ private boolean mGluePending;
+
public EmphasizedNotificationButton(Context context) {
this(context, null);
}
@@ -58,10 +80,25 @@
public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
mRipple = (RippleDrawable) getBackground();
mRipple.mutate();
DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
mBackground = (GradientDrawable) inset.getDrawable();
+
+ mIconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_icon_drawable_size);
+
+ try (TypedArray typedArray = context.obtainStyledAttributes(
+ attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes)) {
+ mInitialDrawablePadding = typedArray.getDimensionPixelSize(
+ android.R.styleable.TextView_drawablePadding, 0);
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "iconSize = " + mIconSize + "px, "
+ + "initialDrawablePadding = " + mInitialDrawablePadding + "px");
+ }
}
@RemotableViewMethod
@@ -95,19 +132,248 @@
return () -> setImageDrawable(drawable);
}
- private void setImageDrawable(Drawable drawable) {
+ private void setImageDrawable(@Nullable Drawable drawable) {
if (drawable != null) {
- drawable.mutate();
- drawable.setTintList(getTextColors());
- drawable.setTintBlendMode(BlendMode.SRC_IN);
- int iconSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_actions_icon_drawable_size);
- drawable.setBounds(0, 0, iconSize, iconSize);
+ prepareIcon(drawable);
}
setCompoundDrawablesRelative(drawable, null, null, null);
}
/**
+ * Sets an icon to be 'glued' to the label when this button is displayed, so the icon will stay
+ * with the text if the button is wider than needed and the text isn't start-aligned.
+ *
+ * As with {@link #setImageIcon(Icon)}, the Icon will have its size constrained and will be set
+ * to the same color as the text, and this must be called after {@link #setTextColor(int)} for
+ * the latter to work.
+ *
+ * This must be called along with {@link #glueLabel(CharSequence)}, in any order, before the
+ * button is displayed.
+ */
+ @RemotableViewMethod(asyncImpl = "glueIconAsync")
+ public void glueIcon(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ setIconToGlue(drawable);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable glueIconAsync(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ return () -> setIconToGlue(drawable);
+ }
+
+ private void setIconToGlue(@Nullable Drawable icon) {
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
+ return;
+ }
+
+ prepareIcon(icon);
+
+ mIconToGlue = icon;
+ mGluePending = true;
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ private void prepareIcon(@NonNull Drawable drawable) {
+ drawable.mutate();
+ drawable.setTintList(getTextColors());
+ drawable.setTintBlendMode(BlendMode.SRC_IN);
+ drawable.setBounds(0, 0, mIconSize, mIconSize);
+ }
+
+ /**
+ * Sets a label to be 'glued' to the icon when this button is displayed, so the icon will stay
+ * with the text if the button is wider than needed and the text isn't start-aligned.
+ *
+ * This must be called along with {@link #glueIcon(Icon)}, in any order, before the button is
+ * displayed.
+ */
+ @RemotableViewMethod(asyncImpl = "glueLabelAsync")
+ public void glueLabel(@Nullable CharSequence label) {
+ setLabelToGlue(label);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable glueLabelAsync(@Nullable CharSequence label) {
+ return () -> setLabelToGlue(label);
+ }
+
+ private void setLabelToGlue(@Nullable CharSequence label) {
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
+ return;
+ }
+
+ mLabelToGlue = label;
+ mGluePending = true;
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "onRtlPropertiesChanged: layoutDirection = " + layoutDirection + ", "
+ + "gluedLayoutDirection = " + mGluedLayoutDirection);
+ }
+
+ if (layoutDirection != mGluedLayoutDirection) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "onRtlPropertiesChanged: layout direction changed; regluing");
+ }
+ mGluePending = true;
+ }
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ private void glueIconAndLabelIfNeeded() {
+ // Don't need to glue:
+
+ if (!mGluePending) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: glue not pending; doing nothing");
+ }
+ return;
+ }
+
+ if (mIconToGlue == null && mLabelToGlue == null) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: no icon or label to glue; doing nothing");
+ }
+ mGluePending = false;
+ return;
+ }
+
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
+ return;
+ }
+
+ // Not ready to glue yet:
+
+ if (!isLayoutDirectionResolved()) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: "
+ + "layout direction not resolved; doing nothing");
+ }
+ return;
+ }
+
+ // Ready to glue but don't have an icon *and* a label:
+ //
+ // (Note that this will *not* happen while the button is being initialized, since we won't
+ // be ready to glue. This can only happen if the button is initialized and displayed and
+ // *then* someone calls glueIcon or glueLabel.
+
+ if (mIconToGlue == null) {
+ Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
+ return;
+ }
+
+ if (mLabelToGlue == null) {
+ Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
+ return;
+ }
+
+ // Can't glue:
+
+ final int layoutDirection = getLayoutDirection();
+ if (layoutDirection != LAYOUT_DIRECTION_LTR && layoutDirection != LAYOUT_DIRECTION_RTL) {
+ Log.e(TAG, "glueIconAndLabelIfNeeded: "
+ + "resolved layout direction neither LTR nor RTL; "
+ + "doing nothing");
+ return;
+ }
+
+ // No excuses left, let's glue it!
+
+ glueIconAndLabel(layoutDirection);
+
+ mGluePending = false;
+ mGluedLayoutDirection = layoutDirection;
+ }
+
+ // Unicode replacement character
+ private static final String IMAGE_SPAN_TEXT = "\ufffd";
+
+ // Unicode no-break space
+ private static final String SPACER_SPAN_TEXT = "\u00a0";
+
+ private static final String LEFT_TO_RIGHT_ISOLATE = "\u2066";
+ private static final String RIGHT_TO_LEFT_ISOLATE = "\u2067";
+ private static final String FIRST_STRONG_ISOLATE = "\u2068";
+ private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";
+
+ private void glueIconAndLabel(int layoutDirection) {
+ final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "glueIconAndLabel: "
+ + "icon = " + mIconToGlue + ", "
+ + "iconSize = " + mIconSize + "px, "
+ + "initialDrawablePadding = " + mInitialDrawablePadding + "px, "
+ + "labelToGlue.length = " + mLabelToGlue.length() + ", "
+ + "rtlLayout = " + rtlLayout);
+ }
+
+ logIfTextDirectionNotFirstStrong();
+
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+
+ // The text direction of the label might not match the layout direction of the button, so
+ // wrap the entire string in a LEFT-TO-RIGHT ISOLATE or RIGHT-TO-LEFT ISOLATE to match the
+ // layout direction. This puts the icon, padding, and label in the right order.
+ builder.append(rtlLayout ? RIGHT_TO_LEFT_ISOLATE : LEFT_TO_RIGHT_ISOLATE);
+
+ appendSpan(builder, IMAGE_SPAN_TEXT, new ImageSpan(mIconToGlue, ALIGN_CENTER));
+ appendSpan(builder, SPACER_SPAN_TEXT, new SpacerSpan(mInitialDrawablePadding));
+
+ // If the text and layout directions are different, we would end up with the *label* in the
+ // wrong direction, so wrap the label in a FIRST STRONG ISOLATE. This triggers the same
+ // automatic text direction heuristic that Android uses by default.
+ builder.append(FIRST_STRONG_ISOLATE);
+
+ appendSpan(builder, mLabelToGlue, new CenterBesideImageSpan(mIconSize));
+
+ builder.append(POP_DIRECTIONAL_ISOLATE);
+ builder.append(POP_DIRECTIONAL_ISOLATE);
+
+ setText(builder);
+ }
+
+ private void logIfTextDirectionNotFirstStrong() {
+ if (!isTextDirectionResolved()) {
+ Log.e(TAG, "glueIconAndLabel: text direction not resolved; "
+ + "letting View assume FIRST STRONG");
+ }
+ final int textDirection = getTextDirection();
+ if (textDirection != TEXT_DIRECTION_FIRST_STRONG) {
+ Log.w(TAG, "glueIconAndLabel: "
+ + "expected text direction TEXT_DIRECTION_FIRST_STRONG "
+ + "but found " + textDirection + "; "
+ + "will use a FIRST STRONG ISOLATE regardless");
+ }
+ }
+
+ private void appendSpan(SpannableStringBuilder builder, CharSequence text, Object span) {
+ final int spanStart = builder.length();
+ builder.append(text);
+ final int spanEnd = builder.length();
+ builder.setSpan(span, spanStart, spanEnd, 0);
+ }
+
+ /**
* Sets whether this view is a priority over its peers (which affects width).
* Specifically, this is used by {@link NotificationActionListLayout} to give this view width
* priority ahead of user-defined buttons when allocating horizontal space.
@@ -123,4 +389,104 @@
public boolean isPriority() {
return mPriority;
}
+
+ private static class SpacerSpan extends ReplacementSpan {
+ private int mWidth;
+
+ SpacerSpan(int width) {
+ mWidth = width;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "width = " + mWidth + "px");
+ }
+ }
+
+
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+ @Nullable Paint.FontMetricsInt fontMetrics) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "getSize returning " + mWidth + "px");
+ }
+
+ return mWidth;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
+ float x, int top, int y, int bottom, @NonNull Paint paint) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "drawing nothing");
+ }
+
+ // Draw nothing, it's a spacer.
+ }
+
+ private static final String TAG = "SpacerSpan";
+ }
+
+ private static class CenterBesideImageSpan extends MetricAffectingSpan {
+ private int mImageHeight;
+
+ private boolean mMeasured;
+ private int mBaselineShiftOffset;
+
+ CenterBesideImageSpan(int imageHeight) {
+ mImageHeight = imageHeight;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "imageHeight = " + mImageHeight + "px");
+ }
+ }
+
+ @Override
+ public void updateMeasureState(@NonNull TextPaint textPaint) {
+ final int textHeight = (int) -textPaint.ascent();
+
+ /*
+ * We only need to shift the text *up* if the text is shorter than the image; ImageSpan
+ * with ALIGN_CENTER will shift the *image* up if the text is taller than the image.
+ */
+ if (textHeight < mImageHeight) {
+ mBaselineShiftOffset = -(mImageHeight - textHeight) / 2;
+ } else {
+ mBaselineShiftOffset = 0;
+ }
+
+ mMeasured = true;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "updateMeasureState: "
+ + "imageHeight = " + mImageHeight + "px, "
+ + "textHeight = " + textHeight + "px, "
+ + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+ }
+
+ textPaint.baselineShift += mBaselineShiftOffset;
+ }
+
+ @Override
+ public void updateDrawState(TextPaint textPaint) {
+ if (textPaint == null) {
+ Log.e(TAG, "updateDrawState: textPaint is null; doing nothing");
+ return;
+ }
+
+ if (!mMeasured) {
+ Log.e(TAG, "updateDrawState: called without measure; doing nothing");
+ return;
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "updateDrawState: "
+ + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+ }
+
+ textPaint.baselineShift += mBaselineShiftOffset;
+ }
+
+ private static final String TAG = "CenterBesideImageSpan";
+ }
+
+ private static final String TAG = "EmphasizedNotificationButton";
}
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index a7a69c9..69d2544 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,12 +16,16 @@
package com.android.internal.widget;
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+
import android.annotation.DimenRes;
import android.app.Notification;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -41,13 +45,13 @@
*/
@RemoteViews.RemoteView
public class NotificationActionListLayout extends LinearLayout {
-
private final int mGravity;
private int mTotalWidth = 0;
private int mExtraStartPadding = 0;
private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
private boolean mEmphasizedMode;
+ private boolean mEvenlyDividedMode;
private int mDefaultPaddingBottom;
private int mDefaultPaddingTop;
private int mEmphasizedPaddingTop;
@@ -124,6 +128,42 @@
}
}
+ private int measureAndReturnEvenlyDividedWidth(int heightMeasureSpec, int innerWidth) {
+ final int numChildren = getChildCount();
+ int childMarginSum = 0;
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ childMarginSum += lp.leftMargin + lp.rightMargin;
+ }
+ }
+
+ final int innerWidthMinusChildMargins = innerWidth - childMarginSum;
+ final int childWidth = innerWidthMinusChildMargins / mNumNotGoneChildren;
+ final int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "measuring evenly divided width: "
+ + "numChildren = " + numChildren + ", "
+ + "innerWidth = " + innerWidth + "px, "
+ + "childMarginSum = " + childMarginSum + "px, "
+ + "innerWidthMinusChildMargins = " + innerWidthMinusChildMargins + "px, "
+ + "childWidth = " + childWidth + "px, "
+ + "childWidthMeasureSpec = " + MeasureSpec.toString(childWidthMeasureSpec));
+ }
+
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ child.measure(childWidthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ return innerWidth;
+ }
+
private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
boolean collapsePriorityActions) {
final int numChildren = getChildCount();
@@ -208,11 +248,16 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
countAndRebuildMeasureOrder();
final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
- int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
- false /* collapsePriorityButtons */);
- if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ int usedWidth;
+ if (mEvenlyDividedMode) {
+ usedWidth = measureAndReturnEvenlyDividedWidth(heightMeasureSpec, innerWidth);
+ } else {
usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
- true /* collapsePriorityButtons */);
+ false /* collapsePriorityButtons */);
+ if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ true /* collapsePriorityButtons */);
+ }
}
mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
@@ -352,6 +397,38 @@
}
/**
+ * Sets whether the available width should be distributed evenly among the action buttons.
+ *
+ * When enabled, the available width (after subtracting this layout's padding and all of the
+ * buttons' margins) is divided by the number of (not-GONE) buttons, and each button is forced
+ * to that exact width, even if it is less <em>or more</em> width than they need.
+ *
+ * When disabled, the available width is allocated as buttons need; if that exceeds the
+ * available width, priority buttons are collapsed to just their icon to save space.
+ *
+ * @param evenlyDividedMode whether to enable evenly divided mode
+ */
+ @RemotableViewMethod
+ public void setEvenlyDividedMode(boolean evenlyDividedMode) {
+ if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
+ + "leaving evenly divided mode disabled");
+ return;
+ }
+
+ if (evenlyDividedMode == mEvenlyDividedMode) {
+ return;
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "evenlyDividedMode changed to " + evenlyDividedMode + "; "
+ + "requesting layout");
+ }
+ mEvenlyDividedMode = evenlyDividedMode;
+ requestLayout();
+ }
+
+ /**
* Set whether the list is in a mode where some actions are emphasized. This will trigger an
* equal measuring where all actions are full height and change a few parameters like
* the padding.
@@ -410,4 +487,5 @@
}
}
+ private static final String TAG = "NotificationActionListLayout";
}
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index ae23942..bed7768 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -75,6 +75,7 @@
jfieldID windowToken;
jfieldID focusTransferTarget;
jfieldID alpha;
+ jfieldID canOccludePresentation;
} gInputWindowHandleClassInfo;
static struct {
@@ -327,6 +328,8 @@
javaObjectForIBinder(env, windowInfo.windowToken));
env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.alpha, windowInfo.alpha);
+ env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.canOccludePresentation,
+ windowInfo.canOccludePresentation);
return inputWindowHandle;
}
@@ -451,6 +454,9 @@
GET_FIELD_ID(gInputWindowHandleClassInfo.alpha, clazz, "alpha", "F");
+ GET_FIELD_ID(gInputWindowHandleClassInfo.canOccludePresentation, clazz,
+ "canOccludePresentation", "Z");
+
jclass surfaceControlClazz;
FIND_CLASS(surfaceControlClazz, "android/view/SurfaceControl");
GET_FIELD_ID(gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject,
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 2f29cae..917d283 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/core/jni/android_opengl_EGL15.cpp b/core/jni/android_opengl_EGL15.cpp
index b9c36b9..447b8ec 100644
--- a/core/jni/android_opengl_EGL15.cpp
+++ b/core/jni/android_opengl_EGL15.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/core/jni/android_opengl_EGLExt.cpp b/core/jni/android_opengl_EGLExt.cpp
index cdc9852..ffd75ea 100644
--- a/core/jni/android_opengl_EGLExt.cpp
+++ b/core/jni/android_opengl_EGLExt.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
@@ -54,11 +55,11 @@
jclass eglsurfaceClassLocal = _env->FindClass("android/opengl/EGLSurface");
eglsurfaceClass = (jclass) _env->NewGlobalRef(eglsurfaceClassLocal);
jclass eglsyncClassLocal = _env->FindClass("android/opengl/EGLSync");
- eglsyncClass = (jclass)_env->NewGlobalRef(eglsyncClassLocal);
+ eglsyncClass = (jclass) _env->NewGlobalRef(eglsyncClassLocal);
egldisplayGetHandleID = _env->GetMethodID(egldisplayClass, "getNativeHandle", "()J");
eglsurfaceGetHandleID = _env->GetMethodID(eglsurfaceClass, "getNativeHandle", "()J");
- eglsyncGetHandleID = _env->GetMethodID(eglsyncClassLocal, "getNativeHandle", "()J");
+ eglsyncGetHandleID = _env->GetMethodID(eglsyncClass, "getNativeHandle", "()J");
}
static void *
@@ -72,6 +73,14 @@
return reinterpret_cast<void*>(_env->CallLongMethod(obj, mid));
}
+// TODO: this should be generated from the .spec file, but needs to be renamed and made private
+static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
+ EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
+ EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
+
+ return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
+}
+
// --------------------------------------------------------------------------
/* EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time ) */
static jboolean
@@ -89,21 +98,12 @@
return (jboolean)_returnValue;
}
-static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
- EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
- EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
-
- return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
-}
-
static const char *classPathName = "android/opengl/EGLExt";
static const JNINativeMethod methods[] = {
- {"_nativeClassInit", "()V", (void *)nativeClassInit},
- {"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z",
- (void *)android_eglPresentationTimeANDROID},
- {"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I",
- (void *)android_eglDupNativeFenceFDANDROID},
+{"_nativeClassInit", "()V", (void*)nativeClassInit },
+{"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z", (void *) android_eglPresentationTimeANDROID },
+{"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I", (void *)android_eglDupNativeFenceFDANDROID },
};
int register_android_opengl_jni_EGLExt(JNIEnv *_env)
diff --git a/core/jni/android_opengl_GLES10.cpp b/core/jni/android_opengl_GLES10.cpp
index d65b498..2d921ad 100644
--- a/core/jni/android_opengl_GLES10.cpp
+++ b/core/jni/android_opengl_GLES10.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES10Ext.cpp b/core/jni/android_opengl_GLES10Ext.cpp
index 3638b87..35a9a68 100644
--- a/core/jni/android_opengl_GLES10Ext.cpp
+++ b/core/jni/android_opengl_GLES10Ext.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11.cpp b/core/jni/android_opengl_GLES11.cpp
index 9724e6c..e04b56e 100644
--- a/core/jni/android_opengl_GLES11.cpp
+++ b/core/jni/android_opengl_GLES11.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11Ext.cpp b/core/jni/android_opengl_GLES11Ext.cpp
index 1ffa4ec..bccbda6 100644
--- a/core/jni/android_opengl_GLES11Ext.cpp
+++ b/core/jni/android_opengl_GLES11Ext.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
index d832558..165262e 100644
--- a/core/jni/android_opengl_GLES20.cpp
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES2/gl2.h>
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index 719c6b3..d3fe439 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES3/gl3.h>
diff --git a/core/jni/android_opengl_GLES31.cpp b/core/jni/android_opengl_GLES31.cpp
index afe7c63..b123f9d 100644
--- a/core/jni/android_opengl_GLES31.cpp
+++ b/core/jni/android_opengl_GLES31.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <stdint.h>
diff --git a/core/jni/android_opengl_GLES31Ext.cpp b/core/jni/android_opengl_GLES31Ext.cpp
index 8127433..1e4049b 100644
--- a/core/jni/android_opengl_GLES31Ext.cpp
+++ b/core/jni/android_opengl_GLES31Ext.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES3/gl31.h>
diff --git a/core/jni/android_opengl_GLES32.cpp b/core/jni/android_opengl_GLES32.cpp
index 7ed7548..e0175f0 100644
--- a/core/jni/android_opengl_GLES32.cpp
+++ b/core/jni/android_opengl_GLES32.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <stdint.h>
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 239c626..aae0da9 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-#include <input/Input.h>
+#include "android_view_InputDevice.h"
#include <android_runtime/AndroidRuntime.h>
+#include <com_android_input_flags.h>
+#include <input/Input.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-
#include <nativehelper/ScopedLocalRef.h>
-#include "android_view_InputDevice.h"
#include "android_view_KeyCharacterMap.h"
-
#include "core_jni_helpers.h"
namespace android {
@@ -34,6 +33,7 @@
jmethodID ctor;
jmethodID addMotionRange;
+ jmethodID setShouldSmoothScroll;
} gInputDeviceClassInfo;
jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& deviceInfo) {
@@ -103,6 +103,18 @@
}
}
+ if (com::android::input::flags::input_device_view_behavior_api()) {
+ const InputDeviceViewBehavior& viewBehavior = deviceInfo.getViewBehavior();
+ std::optional<bool> defaultSmoothScroll = viewBehavior.shouldSmoothScroll;
+ if (defaultSmoothScroll.has_value()) {
+ env->CallVoidMethod(inputDeviceObj.get(), gInputDeviceClassInfo.setShouldSmoothScroll,
+ *defaultSmoothScroll);
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
+ }
+ }
+
return env->NewLocalRef(inputDeviceObj.get());
}
@@ -118,6 +130,8 @@
gInputDeviceClassInfo.addMotionRange =
GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V");
+ gInputDeviceClassInfo.setShouldSmoothScroll =
+ GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "setShouldSmoothScroll", "(Z)V");
return 0;
}
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index 21de723..ef29c88 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index d8305f0..56ab034 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -19,6 +19,9 @@
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -42,6 +45,8 @@
import android.os.Parcel;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.VibrationEffect;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.MediaStore.Audio.AudioColumns;
import android.test.mock.MockContentResolver;
import android.util.Xml;
@@ -55,6 +60,7 @@
import com.google.common.base.Strings;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,10 +69,15 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NotificationChannelTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final String CLASS = "android.app.NotificationChannel";
Context mContext;
@@ -294,28 +305,12 @@
NotificationChannel channel = new NotificationChannel("id", "name", 3);
channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
- TypedXmlSerializer serializer = Xml.newFastSerializer();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
- serializer.startDocument(null, true);
-
// mock the canonicalize in writeXmlForBackup -> getSoundForBackup
when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
.thenReturn(uriToBeRestoredCanonicalized);
when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
.thenReturn(uriToBeRestoredCanonicalized);
- channel.writeXmlForBackup(serializer, mContext);
- serializer.endDocument();
- serializer.flush();
-
- TypedXmlPullParser parser = Xml.newFastPullParser();
- byte[] byteArray = baos.toByteArray();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
- parser.nextTag();
-
- NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
-
MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
cursor.addRow(new Object[] {100L});
@@ -350,7 +345,263 @@
when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
.thenReturn(uriAfterRestoredCanonicalized);
- targetChannel.populateFromXmlForRestore(parser, true, mContext);
- assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+ NotificationChannel restoredChannel = backUpAndRestore(channel);
+ assertThat(restoredChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+ }
+
+ @Test
+ public void testVibrationGetters_nonPatternBasedVibrationEffect_waveform() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ // Note that the amplitude used (1) is not the default amplitude, meaning that this effect
+ // does not have an equivalent pattern based effect.
+ VibrationEffect effect = VibrationEffect.createOneShot(123, 1);
+
+ channel.setVibrationEffect(effect);
+
+ Consumer<NotificationChannel> assertions = (testedChannel) -> {
+ assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+ assertNull(testedChannel.getVibrationPattern());
+ assertTrue(testedChannel.shouldVibrate());
+ };
+ assertions.accept(channel);
+ assertions.accept(writeToAndReadFromParcel(channel));
+ assertions.accept(backUpAndRestore(channel));
+ }
+
+ @Test
+ public void testVibrationGetters_nonPatternBasedVibrationEffect_nonWaveform() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ VibrationEffect effect =
+ VibrationEffect
+ .startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose();
+
+ channel.setVibrationEffect(effect);
+
+ Consumer<NotificationChannel> assertions = (testedChannel) -> {
+ assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+ assertNull(testedChannel.getVibrationPattern()); // amplitude not default.
+ assertTrue(testedChannel.shouldVibrate());
+ };
+ assertions.accept(channel);
+ assertions.accept(writeToAndReadFromParcel(channel));
+ assertions.accept(backUpAndRestore(channel));
+ }
+
+ @Test
+ public void testVibrationGetters_patternBasedVibrationEffect_nonRepeating() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ long[] pattern = new long[] {1, 2};
+ VibrationEffect effect = VibrationEffect.createWaveform(pattern, /* repeatIndex= */ -1);
+
+ channel.setVibrationEffect(effect);
+
+ Consumer<NotificationChannel> assertions = (testedChannel) -> {
+ assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+ assertTrue(Arrays.equals(pattern, testedChannel.getVibrationPattern()));
+ assertTrue(testedChannel.shouldVibrate());
+ };
+ assertions.accept(channel);
+ assertions.accept(writeToAndReadFromParcel(channel));
+ assertions.accept(backUpAndRestore(channel));
+ }
+
+ @Test
+ public void testVibrationGetters_patternBasedVibrationEffect_wholeRepeating() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ long[] pattern = new long[] {1, 2};
+ VibrationEffect effect = VibrationEffect.createWaveform(pattern, /* repeatIndex= */ 0);
+
+ channel.setVibrationEffect(effect);
+
+ Consumer<NotificationChannel> assertions = (testedChannel) -> {
+ assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+ assertNull(testedChannel.getVibrationPattern());
+ assertTrue(testedChannel.shouldVibrate());
+ };
+ assertions.accept(channel);
+ assertions.accept(writeToAndReadFromParcel(channel));
+ assertions.accept(backUpAndRestore(channel));
+ }
+
+ @Test
+ public void testVibrationGetters_patternBasedVibrationEffect_partialRepeating()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ long[] pattern = new long[] {1, 2, 3, 4};
+ VibrationEffect effect = VibrationEffect.createWaveform(pattern, /* repeatIndex= */ 2);
+
+ channel.setVibrationEffect(effect);
+
+ Consumer<NotificationChannel> assertions = (testedChannel) -> {
+ assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+ assertNull(testedChannel.getVibrationPattern());
+ assertTrue(testedChannel.shouldVibrate());
+ };
+ assertions.accept(channel);
+ assertions.accept(writeToAndReadFromParcel(channel));
+ assertions.accept(backUpAndRestore(channel));
+ }
+
+ @Test
+ public void testVibrationGetters_nullVibrationEffect() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ channel.setVibrationEffect(null);
+
+ Consumer<NotificationChannel> assertions = (testedChannel) -> {
+ assertNull(channel.getVibrationEffect());
+ assertNull(channel.getVibrationPattern());
+ assertFalse(channel.shouldVibrate());
+ };
+ assertions.accept(channel);
+ assertions.accept(writeToAndReadFromParcel(channel));
+ assertions.accept(backUpAndRestore(channel));
+ }
+
+ @Test
+ public void testVibrationGetters_nullPattern() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ channel.setVibrationPattern(null);
+
+ Consumer<NotificationChannel> assertions = (testedChannel) -> {
+ assertThat(testedChannel.getVibrationEffect()).isNull();
+ assertNull(testedChannel.getVibrationPattern());
+ assertFalse(testedChannel.shouldVibrate());
+ };
+ assertions.accept(channel);
+ assertions.accept(writeToAndReadFromParcel(channel));
+ assertions.accept(backUpAndRestore(channel));
+ }
+
+ @Test
+ public void testVibrationGetters_setEffectOverridesSetPattern() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(123, VibrationEffect.DEFAULT_AMPLITUDE);
+
+ channel.setVibrationPattern(new long[] {60, 80});
+ channel.setVibrationEffect(effect);
+
+ assertThat(channel.getVibrationEffect()).isEqualTo(effect);
+ assertTrue(Arrays.equals(new long[] {0, 123}, channel.getVibrationPattern()));
+ assertTrue(channel.shouldVibrate());
+
+ channel.setVibrationEffect(null);
+
+ assertNull(channel.getVibrationEffect());
+ assertNull(channel.getVibrationPattern());
+ assertFalse(channel.shouldVibrate());
+ }
+
+ @Test
+ public void testVibrationGetters_setPatternOverridesSetEffect() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ long[] pattern = new long[] {0, 123};
+
+ channel.setVibrationEffect(
+ VibrationEffect.createOneShot(123, VibrationEffect.DEFAULT_AMPLITUDE));
+ channel.setVibrationPattern(pattern);
+
+ assertThat(channel.getVibrationEffect())
+ .isEqualTo(VibrationEffect.createWaveform(pattern, -1));
+ assertTrue(Arrays.equals(pattern, channel.getVibrationPattern()));
+ assertTrue(channel.shouldVibrate());
+
+ channel.setVibrationPattern(null);
+
+ assertNull(channel.getVibrationEffect());
+ assertNull(channel.getVibrationPattern());
+ assertFalse(channel.shouldVibrate());
+ }
+
+ @Test
+ public void testEqualityDependsOnVibration() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel1 = new NotificationChannel("id", "name", 3);
+ NotificationChannel channel2 = new NotificationChannel("id", "name", 3);
+ assertThat(channel1).isEqualTo(channel2);
+
+ VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP);
+ long[] pattern = new long[] {1, 2, 3};
+ channel1.setVibrationEffect(effect);
+ channel2.setVibrationEffect(effect);
+ assertThat(channel1).isEqualTo(channel2);
+
+ channel1.setVibrationPattern(pattern);
+ channel2.setVibrationPattern(pattern);
+ assertThat(channel1).isEqualTo(channel2);
+
+ channel1.setVibrationPattern(pattern);
+ channel2.setVibrationEffect(VibrationEffect.createWaveform(pattern, /* repeat= */ -1));
+ // Channels should still be equal, because the pattern and the effect set are equivalent.
+ assertThat(channel1).isEqualTo(channel2);
+
+ channel1.setVibrationEffect(effect);
+ channel2.setVibrationPattern(pattern);
+ assertThat(channel1).isNotEqualTo(channel2);
+ }
+
+ @Test
+ public void testSetVibrationPattern_flagOn_setsEffect() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ long[] pattern = new long[] {1, 2, 3};
+
+ channel.setVibrationPattern(pattern);
+
+ assertThat(channel.getVibrationEffect())
+ .isEqualTo(VibrationEffect.createWaveform(pattern, -1));
+ }
+
+ @Test
+ public void testSetVibrationPattern_flagNotOn_doesNotSetEffect() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ channel.setVibrationPattern(new long[] {1, 2, 3});
+
+ assertNull(channel.getVibrationEffect());
+ }
+
+ /** Backs up a given channel to an XML, and returns the channel read from the XML. */
+ private NotificationChannel backUpAndRestore(NotificationChannel channel) throws Exception {
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+
+ channel.writeXmlForBackup(serializer, mContext);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ byte[] byteArray = baos.toByteArray();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+ parser.nextTag();
+
+ NotificationChannel restoredChannel =
+ new NotificationChannel("default_id", "default_name", 3);
+ restoredChannel.populateFromXmlForRestore(parser, true, mContext);
+
+ return restoredChannel;
+ }
+
+ private NotificationChannel writeToAndReadFromParcel(NotificationChannel channel) {
+ Parcel parcel = Parcel.obtain();
+ channel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return NotificationChannel.CREATOR.createFromParcel(parcel);
}
}
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
index bd2f36f..d57f1fc 100644
--- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -658,6 +658,52 @@
Truth.assertThat(matchingPackets).hasSize(1);
}
+ @Test
+ public void canTraceOnFlush() throws InvalidProtocolBufferException, InterruptedException {
+ final int singleIntValue = 101;
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> sTestDataSource.trace(ctx -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, singleIntValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+
+ ctx.flush();
+ }),
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == singleIntValue).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
interface RunnableCreator {
Runnable create(int state, AtomicInteger stateOut);
}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index ade20d2..1fd1003 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -66,6 +66,12 @@
src: "preinstalled-packages-strict-signature.xml",
}
+prebuilt_etc {
+ name: "enhanced-confirmation.xml",
+ sub_dir: "sysconfig",
+ src: "enhanced-confirmation.xml",
+}
+
// Privapp permission whitelist files
prebuilt_etc {
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
new file mode 100644
index 0000000..4a9dd2f
--- /dev/null
+++ b/data/etc/enhanced-confirmation.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<!--
+This XML defines an allowlist of packages that should be exempt from ECM (Enhanced Confirmation
+Mode).
+
+Example usage:
+
+ <enhanced-confirmation-trusted-installer
+ package="com.example.app"
+ signature="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+
+This indicates that "com.example.app" should be exempt from ECM, and that, if "com.example.app" is
+an installer, all packages installed via "com.example.app" will also be exempt from ECM.
+-->
+
+<config></config>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d0db708..a43a951 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -259,7 +259,7 @@
/** One handed mode controller to register transition listener. */
private final Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
- private final Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
@@ -285,7 +285,7 @@
BubblePositioner positioner,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -463,7 +463,7 @@
});
mOneHandedOptional.ifPresent(this::registerOneHandedState);
- mDragAndDropController.ifPresent(controller -> controller.addListener(this::collapseStack));
+ mDragAndDropController.addListener(this::collapseStack);
// Clear out any persisted bubbles on disk that no longer have a valid user.
List<UserInfo> users = mUserManager.getAliveUsers();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index b52a118..d4ed017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -83,7 +83,6 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -94,8 +93,8 @@
SystemWindows systemWindows) {
return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
- displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
- mainHandler, systemWindows);
+ displayImeController, displayInsetsController, transitions, transactionPool,
+ iconProvider, recentTasks, launchAdjacentController, mainExecutor, mainHandler,
+ systemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index fc97c798..0d6a852 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -78,7 +78,6 @@
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -203,20 +202,6 @@
@WMSingleton
@Provides
- static Optional<DragAndDropController> provideDragAndDropController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
- shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
- }
-
- @WMSingleton
- @Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
Context context,
ShellInit shellInit,
@@ -911,7 +896,6 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropControllerOptional,
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
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 36f06e8..ead5ad2 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
@@ -172,7 +172,7 @@
BubblePositioner positioner,
DisplayController displayController,
@DynamicOverride Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -338,7 +338,7 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -553,6 +553,24 @@
}
//
+ // Drag and drop
+ //
+
+ @WMSingleton
+ @Provides
+ static DragAndDropController provideDragAndDropController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ ShellCommandHandler shellCommandHandler,
+ DisplayController displayController,
+ UiEventLogger uiEventLogger,
+ IconProvider iconProvider,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DragAndDropController(context, shellInit, shellController,
+ shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor);
+ }
+
+ //
// Misc
//
@@ -562,6 +580,7 @@
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
+ DragAndDropController dragAndDropController,
DefaultMixedHandler defaultMixedHandler) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index fdfb6f3..269c369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -105,25 +105,7 @@
void onDragStarted();
}
- /**
- * Creates {@link DragAndDropController}. Returns {@code null} if the feature is disabled.
- */
- public static DragAndDropController create(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- ShellExecutor mainExecutor) {
- if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
- return null;
- }
- return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
- displayController, uiEventLogger, iconProvider, mainExecutor);
- }
-
- DragAndDropController(Context context,
+ public DragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 70cb2fc..1b124c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -184,7 +184,7 @@
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
- private final Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
@@ -214,7 +214,7 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -290,7 +290,7 @@
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
- mDragAndDropController = Optional.of(dragAndDropController);
+ mDragAndDropController = dragAndDropController;
mTransitions = transitions;
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
@@ -328,7 +328,9 @@
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
}
- mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+ if (mDragAndDropController != null) {
+ mDragAndDropController.setSplitScreenController(this);
+ }
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index e8894a83..b60e361 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -451,6 +451,8 @@
mPendingResize.onConsumed(aborted);
mPendingResize = null;
}
+
+ // TODO: handle transition consumed for active remote handler
}
void onFinish(WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index c101425..aec4d11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -74,7 +74,6 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -85,7 +84,7 @@
SystemWindows systemWindows) {
super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
syncQueue, rootTDAOrganizer, displayController, displayImeController,
- displayInsetsController, dragAndDropController, transitions, transactionPool,
+ displayInsetsController, null, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
Optional.empty(), mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4355ed2..94519a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -74,6 +74,9 @@
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Finished one-shot remote transition %s for (#%d).", mRemote,
+ info.getDebugId());
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
@@ -82,8 +85,8 @@
}
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
+ mRemote = null;
});
- mRemote = null;
}
};
Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -115,17 +118,24 @@
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Merging registered One-shot remote"
+ + " transition %s for (#%d).", mRemote, info.getDebugId());
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Finished merging one-shot remote transition %s for (#%d).", mRemote,
+ info.getDebugId());
// We have merged, since we sent the transaction over binder, the one in this
// process won't be cleared if the remote applied it. We don't actually know if the
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
t.clear();
- mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
- mRemote = null;
+ mMainExecutor.execute(() -> {
+ finishCallback.onTransitionFinished(wct);
+ mRemote = null;
+ });
}
};
try {
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 295f4dc..e5bdeee 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -763,41 +763,41 @@
}; // namespace CanvasJNI
static const JNINativeMethod gMethods[] = {
- {"nGetNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
- {"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches},
- {"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches},
- {"nSetCompatibilityVersion", "(I)V", (void*) CanvasJNI::setCompatibilityVersion},
+ {"nGetNativeFinalizer", "()J", (void*)CanvasJNI::getNativeFinalizer},
+ {"nFreeCaches", "()V", (void*)CanvasJNI::freeCaches},
+ {"nFreeTextLayoutCaches", "()V", (void*)CanvasJNI::freeTextLayoutCaches},
+ {"nSetCompatibilityVersion", "(I)V", (void*)CanvasJNI::setCompatibilityVersion},
- // ------------ @FastNative ----------------
- {"nInitRaster", "(J)J", (void*) CanvasJNI::initRaster},
- {"nSetBitmap", "(JJ)V", (void*) CanvasJNI::setBitmap},
- {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+ // ------------ @FastNative ----------------
+ {"nInitRaster", "(J)J", (void*)CanvasJNI::initRaster},
+ {"nSetBitmap", "(JJ)V", (void*)CanvasJNI::setBitmap},
+ {"nGetClipBounds", "(JLandroid/graphics/Rect;)Z", (void*)CanvasJNI::getClipBounds},
- // ------------ @CriticalNative ----------------
- {"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
- {"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
- {"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
- {"nSave","(JI)I", (void*) CanvasJNI::save},
- {"nSaveLayer","(JFFFFJ)I", (void*) CanvasJNI::saveLayer},
- {"nSaveLayerAlpha","(JFFFFI)I", (void*) CanvasJNI::saveLayerAlpha},
- {"nSaveUnclippedLayer","(JIIII)I", (void*) CanvasJNI::saveUnclippedLayer},
- {"nRestoreUnclippedLayer","(JIJ)V", (void*) CanvasJNI::restoreUnclippedLayer},
- {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
- {"nRestore","(J)Z", (void*) CanvasJNI::restore},
- {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
- {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
- {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
- {"nConcat","(JJ)V", (void*) CanvasJNI::concat},
- {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
- {"nRotate","(JF)V", (void*) CanvasJNI::rotate},
- {"nScale","(JFF)V", (void*) CanvasJNI::scale},
- {"nSkew","(JFF)V", (void*) CanvasJNI::skew},
- {"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
- {"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
- {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
- {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
- {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
- {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
+ // ------------ @CriticalNative ----------------
+ {"nIsOpaque", "(J)Z", (void*)CanvasJNI::isOpaque},
+ {"nGetWidth", "(J)I", (void*)CanvasJNI::getWidth},
+ {"nGetHeight", "(J)I", (void*)CanvasJNI::getHeight},
+ {"nSave", "(JI)I", (void*)CanvasJNI::save},
+ {"nSaveLayer", "(JFFFFJ)I", (void*)CanvasJNI::saveLayer},
+ {"nSaveLayerAlpha", "(JFFFFI)I", (void*)CanvasJNI::saveLayerAlpha},
+ {"nSaveUnclippedLayer", "(JIIII)I", (void*)CanvasJNI::saveUnclippedLayer},
+ {"nRestoreUnclippedLayer", "(JIJ)V", (void*)CanvasJNI::restoreUnclippedLayer},
+ {"nGetSaveCount", "(J)I", (void*)CanvasJNI::getSaveCount},
+ {"nRestore", "(J)Z", (void*)CanvasJNI::restore},
+ {"nRestoreToCount", "(JI)V", (void*)CanvasJNI::restoreToCount},
+ {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
+ {"nSetMatrix", "(JJ)V", (void*)CanvasJNI::setMatrix},
+ {"nConcat", "(JJ)V", (void*)CanvasJNI::concat},
+ {"nConcat", "(J[F)V", (void*)CanvasJNI::concat44},
+ {"nRotate", "(JF)V", (void*)CanvasJNI::rotate},
+ {"nScale", "(JFF)V", (void*)CanvasJNI::scale},
+ {"nSkew", "(JFF)V", (void*)CanvasJNI::skew},
+ {"nTranslate", "(JFF)V", (void*)CanvasJNI::translate},
+ {"nQuickReject", "(JJ)Z", (void*)CanvasJNI::quickRejectPath},
+ {"nQuickReject", "(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+ {"nClipRect", "(JFFFFI)Z", (void*)CanvasJNI::clipRect},
+ {"nClipPath", "(JJI)Z", (void*)CanvasJNI::clipPath},
+ {"nSetDrawFilter", "(JJ)V", (void*)CanvasJNI::setPaintFilter},
};
// If called from Canvas these are regular JNI
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 84c197d..be93abb 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -111,7 +111,7 @@
void resumeRecording(in IBinder sessionToken, in Bundle params, int userId);
// For playback control
- void startPlayback(in IBinder sessionToken, int userId);
+ void resumePlayback(in IBinder sessionToken, int userId);
void stopPlayback(in IBinder sessionToken, int mode, int userId);
// For broadcast info
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 7b20c39..6b247cc 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -63,7 +63,7 @@
void timeShiftSetMode(int mode);
void timeShiftEnablePositionTracking(boolean enable);
- void startPlayback();
+ void resumePlayback();
void stopPlayback(int mode);
// For the recording session
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 999e2cf..d145397 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -80,7 +80,7 @@
private static final int DO_SET_TV_MESSAGE_ENABLED = 31;
private static final int DO_NOTIFY_TV_MESSAGE = 32;
private static final int DO_STOP_PLAYBACK = 33;
- private static final int DO_START_PLAYBACK = 34;
+ private static final int DO_RESUME_PLAYBACK = 34;
private static final int DO_SET_VIDEO_FROZEN = 35;
private static final int DO_NOTIFY_AD_SESSION_DATA = 36;
@@ -295,8 +295,8 @@
mTvInputSessionImpl.stopPlayback(msg.arg1);
break;
}
- case DO_START_PLAYBACK: {
- mTvInputSessionImpl.startPlayback();
+ case DO_RESUME_PLAYBACK: {
+ mTvInputSessionImpl.resumePlayback();
break;
}
case DO_SET_VIDEO_FROZEN: {
@@ -523,8 +523,8 @@
}
@Override
- public void startPlayback() {
- mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_PLAYBACK));
+ public void resumePlayback() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RESUME_PLAYBACK));
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 8720bfe..672f58b 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -17,6 +17,7 @@
package android.media.tv;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -35,6 +36,7 @@
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.ad.TvAdManager;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.TvInteractiveAppManager;
import android.net.Uri;
import android.os.Binder;
@@ -131,7 +133,8 @@
VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION, VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING,
VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD, VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE,
VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID, VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT,
- VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING, VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN})
+ VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING, VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN,
+ VIDEO_UNAVAILABLE_REASON_STOPPED})
public @interface VideoUnavailableReason {}
/** Indicates that this TV message contains watermarking data */
@@ -344,9 +347,9 @@
/**
* Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
* {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
- * it has been stopped by stopPlayback.
- * @hide
+ * it has been stopped by {@link TvView#stopPlayback(int)}.
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public static final int VIDEO_UNAVAILABLE_REASON_STOPPED = 19;
/** @hide */
@@ -3616,13 +3619,13 @@
}
}
- void startPlayback() {
+ void resumePlayback() {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
- mService.startPlayback(mToken, mUserId);
+ mService.resumePlayback(mToken, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index a022b1c..6b03041 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.MainThread;
@@ -35,6 +36,7 @@
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.ad.TvAdManager;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.TvInteractiveAppService;
import android.net.Uri;
import android.os.AsyncTask;
@@ -1615,20 +1617,20 @@
* <p> Note that this is different form {@link #timeShiftPause()} as should release the
* stream, making it impossible to resume from this position again.
* @param mode
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void onStopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) {
}
/**
- * Starts playback of the Audio, Video, and CC streams.
+ * Resumes playback of the Audio, Video, and CC streams.
*
* <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be
- * used after stopping playback. This is used to restart playback from the current position
+ * used after stopping playback. This is used to resume playback from the current position
* in the live broadcast.
- * @hide
*/
- public void onStartPlayback() {
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onResumePlayback() {
}
/**
@@ -2112,10 +2114,10 @@
}
/**
- * Calls {@link #onStartPlayback()}.
+ * Calls {@link #onResumePlayback()}.
*/
- void startPlayback() {
- onStartPlayback();
+ void resumePlayback() {
+ onResumePlayback();
}
/**
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index cb45661..ffc121e 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +38,7 @@
import android.media.tv.TvInputManager.Session;
import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
import android.media.tv.TvInputManager.SessionCallback;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.TvInteractiveAppService;
import android.net.Uri;
import android.os.Bundle;
@@ -650,10 +652,10 @@
* <p>The metadata that will continue to be filtered includes the PSI
* (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1.
*
- * <p> Note that this is different form {@link #timeShiftPause()} as this completely drops
+ * <p> Note that this is different from {@link #timeShiftPause()} as this completely drops
* the stream, making it impossible to resume from this position again.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void stopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) {
if (mSession != null) {
mSession.stopPlayback(mode);
@@ -661,16 +663,19 @@
}
/**
- * Starts playback of the Audio, Video, and CC streams.
+ * Resumes playback of the Audio, Video, and CC streams.
*
- * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be
- * used after stopping playback. This is used to restart playback from the current position
- * in the live broadcast.
- * @hide
+ * <p> Note that this is different from {@link #timeShiftResume()} as this is intended to
+ * be used after {@link #stopPlayback(int)} has been called. This is used to resume
+ * playback from the current position in the live broadcast.
+
+ * <p> If this is the first time playback should begin, you will need to use
+ * {@link #tune(String, Uri, Bundle)} to begin playback.
*/
- public void startPlayback() {
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void resumePlayback() {
if (mSession != null) {
- mSession.startPlayback();
+ mSession.resumePlayback();
}
}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 02c0d75..f373bed 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -437,11 +437,10 @@
}
/**
- * Returns the complete list of TV AD service on the system.
+ * Returns the complete list of TV AD services on the system.
*
* @return List of {@link TvAdServiceInfo} for each TV AD service that describes its meta
* information.
- * @hide
*/
@NonNull
public List<TvAdServiceInfo> getTvAdServiceList() {
@@ -1174,8 +1173,7 @@
}
/**
- * Callback used to monitor status of the TV AD service.
- * @hide
+ * Callback used to monitor status of the TV advertisement service.
*/
public abstract static class TvAdServiceCallback {
/**
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 4d8f5c8b..953b5cf 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -17,6 +17,7 @@
package android.media.tv.ad;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,6 +33,7 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -61,8 +63,8 @@
/**
* The TvAdService class represents a TV client-side advertisement service.
- * @hide
*/
+@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public abstract class TvAdService extends Service {
private static final boolean DEBUG = false;
private static final String TAG = "TvAdService";
@@ -73,7 +75,6 @@
* Name under which a TvAdService component publishes information about itself. This meta-data
* must reference an XML resource containing an
* <code><{@link android.R.styleable#TvAdService tv-ad-service}></code> tag.
- * @hide
*/
public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
@@ -92,7 +93,7 @@
@Override
@Nullable
- public final IBinder onBind(@NonNull Intent intent) {
+ public final IBinder onBind(@Nullable Intent intent) {
ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
@Override
public void registerCallback(ITvAdServiceCallback cb) {
@@ -398,6 +399,7 @@
* @param data the original bytes to be signed.
*
* @see #onSigningResult(String, byte[])
+ * @hide
*/
@CallSuper
public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
@@ -421,22 +423,22 @@
}
@Override
- public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ public boolean onKeyDown(int keyCode, @Nullable KeyEvent event) {
return false;
}
@Override
- public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
+ public boolean onKeyLongPress(int keyCode, @Nullable KeyEvent event) {
return false;
}
@Override
- public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
+ public boolean onKeyMultiple(int keyCode, int count, @Nullable KeyEvent event) {
return false;
}
@Override
- public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
+ public boolean onKeyUp(int keyCode, @Nullable KeyEvent event) {
return false;
}
@@ -484,6 +486,7 @@
* @param top Top position in pixels, relative to the overlay view.
* @param right Right position in pixels, relative to the overlay view.
* @param bottom Bottom position in pixels, relative to the overlay view.
+ *
*/
@CallSuper
public void layoutSurface(final int left, final int top, final int right,
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
index 45dc89d..bac14e7 100644
--- a/media/java/android/media/tv/ad/TvAdServiceInfo.java
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -16,6 +16,7 @@
package android.media.tv.ad;
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -26,6 +27,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.media.tv.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -42,8 +44,8 @@
/**
* This class is used to specify meta information of a TV AD service.
- * @hide
*/
+@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public final class TvAdServiceInfo implements Parcelable {
private static final boolean DEBUG = false;
private static final String TAG = "TvAdServiceInfo";
@@ -95,6 +97,7 @@
in.readStringList(mTypes);
}
+ @NonNull
public static final Creator<TvAdServiceInfo> CREATOR = new Creator<TvAdServiceInfo>() {
@Override
public TvAdServiceInfo createFromParcel(Parcel in) {
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 604dbd5..be88506 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -17,6 +17,7 @@
package android.media.tv.ad;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -29,6 +30,7 @@
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.media.tv.ad.TvAdManager.Session.FinishedInputEventCallback;
+import android.media.tv.flags.Flags;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -48,9 +50,9 @@
import java.util.concurrent.Executor;
/**
- * Displays contents of TV AD services.
- * @hide
+ * Displays contents of TV advertisement services.
*/
+@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public class TvAdView extends ViewGroup {
private static final String TAG = "TvAdView";
private static final boolean DEBUG = false;
@@ -182,14 +184,12 @@
return true;
}
- /** @hide */
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
createSessionMediaView();
}
- /** @hide */
@Override
public void onDetachedFromWindow() {
removeSessionMediaView();
@@ -381,6 +381,7 @@
* @param event The input event.
* @return If you handled the event, return {@code true}. If you want to allow the event to be
* handled by the next receiver, return {@code false}.
+ * @hide
*/
public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
return false;
@@ -422,7 +423,7 @@
}
@Override
- public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+ public boolean dispatchKeyEvent(@Nullable KeyEvent event) {
if (super.dispatchKeyEvent(event)) {
return true;
}
@@ -465,6 +466,7 @@
/**
* Stops the AD service.
+ * @hide
*/
public void stopAdService() {
if (DEBUG) {
@@ -479,6 +481,7 @@
* Resets the AD service.
*
* <p>This releases the resources of the corresponding {@link TvAdService.Session}.
+ * @hide
*/
public void resetAdService() {
if (DEBUG) {
@@ -493,6 +496,7 @@
* Sends current video bounds to related TV AD service.
*
* @param bounds the rectangle area for rendering the current video.
+ * @hide
*/
public void sendCurrentVideoBounds(@NonNull Rect bounds) {
if (DEBUG) {
@@ -508,6 +512,7 @@
*
* @param channelUri The current channel URI; {@code null} if there is no currently tuned
* channel.
+ * @hide
*/
public void sendCurrentChannelUri(@Nullable Uri channelUri) {
if (DEBUG) {
@@ -520,6 +525,7 @@
/**
* Sends track info list to related TV AD service.
+ * @hide
*/
public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
if (DEBUG) {
@@ -536,6 +542,7 @@
* @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
* tuned.
* @see android.media.tv.TvInputInfo
+ * @hide
*/
public void sendCurrentTvInputId(@Nullable String inputId) {
if (DEBUG) {
@@ -577,6 +584,7 @@
* can also be added to the params.
*
* @see #ERROR_KEY_METHOD_NAME
+ * @hide
*/
public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
if (DEBUG) {
@@ -599,6 +607,7 @@
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
* how to parse this data.
+ * @hide
*/
public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
@NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7b6dc38..6b0620c 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -17,6 +17,7 @@
package android.media.tv.interactive;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -44,6 +45,7 @@
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
import android.net.Uri;
import android.net.http.SslCertificate;
@@ -959,12 +961,12 @@
/**
* Called when the TV App sends the selected track info as a response to
- * requestSelectedTrackInfo.
+ * {@link #requestSelectedTrackInfo()}
*
- * @param tracks
- * @hide
+ * @param tracks A list of {@link TvTrackInfo} that are currently selected
*/
- public void onSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
}
@Override
@@ -1373,13 +1375,13 @@
}
/**
- * Requests the currently selected {@link TvTrackInfo} from the TV App.
+ * Requests a list of the currently selected {@link TvTrackInfo} from the TV App.
*
* <p> Normally, track info cannot be synchronized until the channel has
- * been changed. This is used when the session of the TIAS is newly
- * created and the normal synchronization has not happened yet.
- * @hide
+ * been changed. This is used when the session of the {@link TvInteractiveAppService}
+ * is newly created and the normal synchronization has not happened yet.
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
@CallSuper
public void requestSelectedTrackInfo() {
executeOrPostRunnableOnMainThread(() -> {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 3b29574..584ea84 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -17,6 +17,7 @@
package android.media.tv.interactive;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -30,6 +31,7 @@
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.TvInteractiveAppManager.Session;
import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
@@ -585,8 +587,9 @@
/**
* Sends the currently selected track info to the TV Interactive App.
*
- * @hide
+ * @param tracks list of {@link TvTrackInfo} of the currently selected track(s)
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void sendSelectedTrackInfo(@Nullable List<TvTrackInfo> tracks) {
if (DEBUG) {
Log.d(TAG, "sendSelectedTrackInfo");
@@ -1248,8 +1251,8 @@
* called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) {
}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
index 70202463..c18a2de 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
@@ -41,7 +41,7 @@
public EffectsTest() {
- Log.d(TAG, "contructor");
+ Log.d(TAG, "constructor");
}
@Override
diff --git a/opengl/java/android/opengl/EGLExt.java b/opengl/java/android/opengl/EGLExt.java
index 1570e0e..31104a0 100644
--- a/opengl/java/android/opengl/EGLExt.java
+++ b/opengl/java/android/opengl/EGLExt.java
@@ -46,14 +46,6 @@
_nativeClassInit();
}
- // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
-
- public static native boolean eglPresentationTimeANDROID(
- EGLDisplay dpy,
- EGLSurface sur,
- long time
- );
-
/**
* Retrieves the SyncFence for an EGLSync created with EGL_SYNC_NATIVE_FENCE_ANDROID
*
@@ -83,4 +75,13 @@
}
private static native int eglDupNativeFenceFDANDROIDImpl(EGLDisplay display, EGLSync sync);
+
+ // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
+
+ public static native boolean eglPresentationTimeANDROID(
+ EGLDisplay dpy,
+ EGLSurface sur,
+ long time
+ );
+
}
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index 27ddff9..63e6c50 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -3,7 +3,15 @@
srcs: [
"java/**/*.java",
"java/**/*.aidl",
+ ":statslog-crashrecovery-java-gen",
],
- path: "java",
visibility: ["//frameworks/base:__subpackages__"],
}
+
+genrule {
+ name: "statslog-crashrecovery-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module crashrecovery " +
+ "--javaPackage com.android.server.crashrecovery.proto --javaClass CrashRecoveryStatsLog --worksource",
+ out: ["com/android/server/crashrecovery/proto/CrashRecoveryStatsLog.java"],
+}
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index dd54334..dffe4e2 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -46,11 +46,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import com.android.server.am.SettingsToPropertiesMapper;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import java.io.File;
import java.util.ArrayList;
@@ -390,7 +390,7 @@
return;
}
- FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
+ CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 2007079..50322f0 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -39,13 +39,13 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import com.android.server.SystemConfig;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import com.android.server.pm.ApexManager;
import java.io.BufferedReader;
@@ -418,7 +418,7 @@
final VersionedPackage logPackage = logPackageTemp;
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
reasonToLog, failedPackageToLog);
Consumer<Intent> onResult = result -> {
@@ -430,19 +430,19 @@
int rollbackId = rollback.getRollbackId();
saveStagedRollbackId(rollbackId, logPackage);
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog
+ CrashRecoveryStatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
reasonToLog, failedPackageToLog);
} else {
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog
+ CrashRecoveryStatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
reasonToLog, failedPackageToLog);
}
} else {
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog
+ CrashRecoveryStatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
reasonToLog, failedPackageToLog);
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index f9ef994..898c543 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -16,16 +16,16 @@
package com.android.server.rollback;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,8 +42,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.PackageWatchdog;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import java.util.List;
import java.util.Set;
@@ -197,8 +197,8 @@
+ " rollbackReason: " + rollbackReasonToString(rollbackReason)
+ " failedPackageName: " + failingPackageName);
if (logPackage != null) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ CrashRecoveryStatsLog.write(
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
type,
logPackage.getPackageName(),
logPackage.getVersionCode(),
@@ -208,8 +208,8 @@
} else {
// In the case that the log package is null, still log an empty string as an
// indication that retrieving the logging parent failed.
- FrameworkStatsLog.write(
- FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ CrashRecoveryStatsLog.write(
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
type,
"",
0,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 6cafcf7..bd9d2e6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -32,6 +32,7 @@
import android.os.Bundle
import android.os.ResultReceiver
import android.util.Log
+import android.view.autofill.AutofillManager
import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
@@ -80,9 +81,10 @@
CreateCredentialProviderData::class.java
) ?: emptyList()
RequestInfo.TYPE_GET ->
- intent.extras?.getParcelableArrayList(
- ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
- GetCredentialProviderData::class.java
+ getEnabledProviderDataList(
+ intent
+ ) ?: getEnabledProviderDataListFromAuthExtras(
+ intent
) ?: emptyList()
else -> {
Log.d(
@@ -238,6 +240,24 @@
)
}
+ private fun getEnabledProviderDataList(intent: Intent): List<GetCredentialProviderData>? {
+ return intent.extras?.getParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ GetCredentialProviderData::class.java
+ )
+ }
+
+ private fun getEnabledProviderDataListFromAuthExtras(
+ intent: Intent
+ ): List<GetCredentialProviderData>? {
+ return intent.getBundleExtra(
+ AutofillManager.EXTRA_AUTH_STATE
+ ) ?.getParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ GetCredentialProviderData::class.java
+ )
+ }
+
// IMPORTANT: new invocation should be mindful that this method can throw.
private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
return CreateFlowUtils.toEnabledProviderList(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 1f1d236..07f1fa3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -16,22 +16,24 @@
package com.android.credentialmanager.autofill
-import android.R
+import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
-import android.app.PendingIntent
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialRequest
-import android.credentials.GetCandidateCredentialsResponse
-import android.credentials.GetCandidateCredentialsException
+import android.credentials.Credential
+import android.credentials.CredentialManager
import android.credentials.CredentialOption
+import android.credentials.GetCandidateCredentialsException
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCredentialRequest
+import android.credentials.GetCredentialResponse
+import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
+import android.credentials.selection.ProviderData
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
import android.provider.Settings
-import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
import android.service.autofill.Field
@@ -44,12 +46,11 @@
import android.service.autofill.SaveRequest
import android.service.credentials.CredentialProviderService
import android.util.Log
+import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import android.view.autofill.IAutoFillManagerClient
-import android.view.autofill.AutofillId
-import android.widget.inline.InlinePresentationSpec
-import android.credentials.CredentialManager
import android.widget.RemoteViews
+import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
@@ -61,10 +62,9 @@
import com.android.credentialmanager.ktx.credentialEntry
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.model.get.ProviderInfo
+import java.util.concurrent.Executors
import org.json.JSONException
import org.json.JSONObject
-import java.util.concurrent.Executors
class CredentialAutofillService : AutofillService() {
@@ -153,8 +153,7 @@
return
}
- val fillResponse = convertToFillResponse(result, request,
- this@CredentialAutofillService)
+ val fillResponse = convertToFillResponse(result, request)
if (fillResponse != null) {
callback.onSuccess(fillResponse)
} else {
@@ -231,7 +230,7 @@
}
private fun getEntryToIconMap(
- candidateProviderDataList: MutableList<GetCredentialProviderData>
+ candidateProviderDataList: List<GetCredentialProviderData>
): Map<String, Icon> {
val entryIconMap: MutableMap<String, Icon> = mutableMapOf()
candidateProviderDataList.forEach { provider ->
@@ -261,20 +260,16 @@
private fun convertToFillResponse(
getCredResponse: GetCandidateCredentialsResponse,
- filLRequest: FillRequest,
- context: Context
+ filLRequest: FillRequest
): FillResponse? {
- val providerList = GetFlowUtils.toProviderList(
- getCredResponse.candidateProviderDataList,
- context)
- if (providerList.isEmpty()) {
+ val candidateProviders = getCredResponse.candidateProviderDataList
+ if (candidateProviders.isEmpty()) {
return null
}
- val entryIconMap: Map<String, Icon> =
- getEntryToIconMap(getCredResponse.candidateProviderDataList)
- val autofillIdToProvidersMap: Map<AutofillId, List<ProviderInfo>> =
- mapAutofillIdToProviders(providerList)
+ val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders)
+ val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
+ mapAutofillIdToProviders(candidateProviders)
val fillResponseBuilder = FillResponse.Builder()
var validFillResponse = false
autofillIdToProvidersMap.forEach { (autofillId, providers) ->
@@ -292,11 +287,14 @@
private fun processProvidersForAutofillId(
filLRequest: FillRequest,
autofillId: AutofillId,
- providerList: List<ProviderInfo>,
+ providerDataList: ArrayList<GetCredentialProviderData>,
entryIconMap: Map<String, Icon>,
fillResponseBuilder: FillResponse.Builder,
bottomSheetPendingIntent: PendingIntent?
): Boolean {
+ val providerList = GetFlowUtils.toProviderList(
+ providerDataList,
+ this@CredentialAutofillService)
if (providerList.isEmpty()) {
return false
}
@@ -340,7 +338,7 @@
return@usernameLoop
}
if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
- return@usernameLoop;
+ return@usernameLoop
}
val icon: Icon = if (primaryEntry.icon == null) {
// The empty entry icon has non-null icon reference but null drawable reference.
@@ -398,19 +396,20 @@
inlinePresentationSpecsCount)
if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
- fillResponseBuilder)
+ fillResponseBuilder, providerDataList)
}
return datasetAdded
}
- private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
- pendingIntent: PendingIntent,
- icon: Icon,
- spec: InlinePresentationSpec,
- duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
- InlinePresentation {
- val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
- && primaryEntry.displayName != null) {
+ private fun createInlinePresentation(
+ primaryEntry: CredentialEntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
+ ): InlinePresentation {
+ val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
+ primaryEntry.displayName != null) {
primaryEntry.displayName!!
} else {
primaryEntry.userName
@@ -430,7 +429,8 @@
private fun addDropdownMoreOptionsPresentation(
bottomSheetPendingIntent: PendingIntent,
autofillId: AutofillId,
- fillResponseBuilder: FillResponse.Builder) {
+ fillResponseBuilder: FillResponse.Builder
+ ) {
val presentationBuilder = Presentations.Builder()
.setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
@@ -460,7 +460,8 @@
bottomSheetPendingIntent: PendingIntent,
spec: InlinePresentationSpec,
autofillId: AutofillId,
- fillResponseBuilder: FillResponse.Builder
+ fillResponseBuilder: FillResponse.Builder,
+ providerDataList: ArrayList<GetCredentialProviderData>
) {
val dataSetBuilder = Dataset.Builder()
val sliceBuilder = InlineSuggestionUi
@@ -471,6 +472,10 @@
.setInlinePresentation(InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ true))
+ val extraBundle = Bundle()
+ extraBundle.putParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
+
fillResponseBuilder.addDataset(
dataSetBuilder
.setField(
@@ -479,6 +484,7 @@
presentationBuilder.build())
.build())
.setAuthentication(bottomSheetPendingIntent.intentSender)
+ .setAuthenticationExtras(extraBundle)
.build()
)
}
@@ -514,16 +520,16 @@
* }
*/
private fun mapAutofillIdToProviders(
- providerList: List<ProviderInfo>
- ): Map<AutofillId, List<ProviderInfo>> {
- val autofillIdToProviders: MutableMap<AutofillId, MutableList<ProviderInfo>> =
- mutableMapOf()
+ providerList: List<GetCredentialProviderData>
+ ): Map<AutofillId, ArrayList<GetCredentialProviderData>> {
+ val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> =
+ mutableMapOf()
providerList.forEach { provider ->
val autofillIdToCredentialEntries:
- MutableMap<AutofillId, MutableList<CredentialEntryInfo>> =
- mapAutofillIdToCredentialEntries(provider.credentialEntryList)
+ MutableMap<AutofillId, ArrayList<Entry>> =
+ mapAutofillIdToCredentialEntries(provider.credentialEntries)
autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
- autofillIdToProviders.getOrPut(autofillId) { mutableListOf() }
+ autofillIdToProviders.getOrPut(autofillId) { ArrayList() }
.add(copyProviderInfo(provider, entries))
}
}
@@ -531,13 +537,13 @@
}
private fun mapAutofillIdToCredentialEntries(
- credentialEntryList: List<CredentialEntryInfo>
- ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> {
+ credentialEntryList: List<Entry>
+ ): MutableMap<AutofillId, ArrayList<Entry>> {
val autofillIdToCredentialEntries:
- MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf()
+ MutableMap<AutofillId, ArrayList<Entry>> = mutableMapOf()
credentialEntryList.forEach entryLoop@{ credentialEntry ->
val autofillId: AutofillId? = credentialEntry
- .fillInIntent
+ .frameworkExtrasIntent
?.getParcelableExtra(
CredentialProviderService.EXTRA_AUTOFILL_ID,
AutofillId::class.java)
@@ -546,24 +552,22 @@
" Integration might be disabled.")
return@entryLoop
}
- autofillIdToCredentialEntries.getOrPut(autofillId) { mutableListOf() }
+ autofillIdToCredentialEntries.getOrPut(autofillId) { ArrayList() }
.add(credentialEntry)
}
return autofillIdToCredentialEntries
}
private fun copyProviderInfo(
- providerInfo: ProviderInfo,
- credentialList: List<CredentialEntryInfo>
- ): ProviderInfo {
- return ProviderInfo(
- providerInfo.id,
- providerInfo.icon,
- providerInfo.displayName,
- credentialList,
- providerInfo.authenticationEntryList,
- providerInfo.remoteEntry,
- providerInfo.actionEntryList
+ providerInfo: GetCredentialProviderData,
+ credentialList: List<Entry>
+ ): GetCredentialProviderData {
+ return GetCredentialProviderData(
+ providerInfo.providerFlattenedComponentName,
+ credentialList,
+ providerInfo.actionChips,
+ providerInfo.authenticationEntries,
+ providerInfo.remoteEntry
)
}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 6f9556f..ec519ca 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.6.0-rc01"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha01"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 1f78a9c..f6fbc02 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
#
[versions]
-agp = "8.2.1"
-compose-compiler = "1.5.1"
+agp = "8.2.2"
+compose-compiler = "1.5.8"
dexmaker-mockito = "2.28.3"
jvm = "17"
-kotlin = "1.9.0"
+kotlin = "1.9.22"
truth = "1.1.5"
[libraries]
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 618dc37..08a8797 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-beta02")
+ api("androidx.compose.material3:material3:1.2.0-rc01")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.7.6")
+ api("androidx.navigation:navigation-compose:2.8.0-alpha01")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 560bc46..bcdb64d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1745,12 +1745,14 @@
final BluetoothDevice tmpDevice = mDevice;
final short tmpRssi = mRssi;
final boolean tmpJustDiscovered = mJustDiscovered;
+ final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
// Set main device from sub device
release();
mDevice = newMainDevice.mDevice;
mRssi = newMainDevice.mRssi;
mJustDiscovered = newMainDevice.mJustDiscovered;
+ mHearingAidInfo = newMainDevice.mHearingAidInfo;
fillData();
// Set sub device from backup
@@ -1758,6 +1760,7 @@
newMainDevice.mDevice = tmpDevice;
newMainDevice.mRssi = tmpRssi;
newMainDevice.mJustDiscovered = tmpJustDiscovered;
+ newMainDevice.mHearingAidInfo = tmpHearingAidInfo;
newMainDevice.fillData();
// Add the sub device back into mMemberDevices with correct hash
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 9db8b47..461ecf5d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1769,8 +1769,10 @@
public void switchMemberDeviceContent_switchMainDevice_switchesSuccessful() {
mCachedDevice.mRssi = RSSI_1;
mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
+ mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
mSubCachedDevice.mRssi = RSSI_2;
mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
+ mSubCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
mCachedDevice.addMemberDevice(mSubCachedDevice);
mCachedDevice.switchMemberDeviceContent(mSubCachedDevice);
@@ -1778,10 +1780,12 @@
assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
+ assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
verify(mCachedDevice).fillData();
assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
+ assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
verify(mSubCachedDevice).fillData();
assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a5b9e9e..ba3026e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -67,6 +67,16 @@
}
flag {
+ name: "nssl_falsing_fix"
+ namespace: "systemui"
+ description: "Minor touch changes to prevent falsing errors in NSSL"
+ bug: "316551193"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "refactor_get_current_user"
namespace: "systemui"
description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index efdbfdb..055252b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -353,7 +353,7 @@
/**
* Return the first [GradientDrawable] found in [drawable], or null if none is found. If
- * [drawable] is a [LayerDrawable], this will return the first layer that is a
+ * [drawable] is a [LayerDrawable], this will return the first layer that has a
* [GradientDrawable].
*/
fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
@@ -367,8 +367,8 @@
if (drawable is LayerDrawable) {
for (i in 0 until drawable.numberOfLayers) {
- val maybeGradient = drawable.getDrawable(i)
- if (maybeGradient is GradientDrawable) {
+ val maybeGradient = findGradientDrawable(drawable.getDrawable(i))
+ if (maybeGradient != null) {
return maybeGradient
}
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 3c32594..13ee196 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -17,7 +17,6 @@
package com.android.systemui.compose
-import android.app.Dialog
import android.content.Context
import android.view.View
import android.view.WindowInsets
@@ -33,7 +32,6 @@
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -90,10 +88,10 @@
throwComposeUnavailableError()
}
- override fun createStickyKeysDialog(
- dialogFactory: SystemUIDialogFactory,
+ override fun createStickyKeysIndicatorContent(
+ context: Context,
viewModel: StickyKeysIndicatorViewModel
- ): Dialog {
+ ): View {
throwComposeUnavailableError()
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index afb860e..f05c7f3 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -16,7 +16,6 @@
package com.android.systemui.compose
-import android.app.Dialog
import android.content.Context
import android.graphics.Point
import android.view.View
@@ -39,7 +38,7 @@
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
-import com.android.systemui.keyboard.stickykeys.ui.view.StickyKeysIndicator
+import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
@@ -50,8 +49,6 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
-import com.android.systemui.statusbar.phone.create
import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
@@ -140,11 +137,11 @@
}
}
- override fun createStickyKeysDialog(
- dialogFactory: SystemUIDialogFactory,
+ override fun createStickyKeysIndicatorContent(
+ context: Context,
viewModel: StickyKeysIndicatorViewModel
- ): Dialog {
- return dialogFactory.create { StickyKeysIndicator(viewModel) }
+ ): View {
+ return createStickyKeyIndicatorView(context, viewModel)
}
override fun createCommunalView(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index 071433e..dd86646 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -16,23 +16,42 @@
package com.android.systemui.keyboard.stickykeys.ui.view
+import android.content.Context
+import android.view.View
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
+import com.android.compose.theme.PlatformTheme
import com.android.systemui.keyboard.stickykeys.shared.model.Locked
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+fun createStickyKeyIndicatorView(context: Context, viewModel: StickyKeysIndicatorViewModel): View {
+ return ComposeView(context).apply {
+ setContent {
+ PlatformTheme {
+ val defaultContentColor = MaterialTheme.colorScheme.onSurfaceVariant
+ CompositionLocalProvider(LocalContentColor provides defaultContentColor) {
+ StickyKeysIndicator(viewModel)
+ }
+ }
+ }
+ }
+}
+
@Composable
fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) {
val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap())
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index a910bca..e40f6b6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.layout.IntermediateMeasureScope
@@ -473,7 +474,8 @@
placeable.place(offset)
} else {
placeable.placeWithLayer(offset) {
- this.alpha = elementAlpha(layoutImpl, element, scene)
+ alpha = elementAlpha(layoutImpl, element, scene)
+ compositingStrategy = CompositingStrategy.ModulateAlpha
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 1642e52..45f98be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -72,7 +72,7 @@
testScope.runTest {
val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
}
@Test
@@ -84,16 +84,16 @@
// Initial value is false.
val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
// Change to media available and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
}
@@ -104,20 +104,20 @@
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Change to media available and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
val mediaModel = collectLastValue(underTest.mediaModel)
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
mediaDataListenerCaptor.value.onMediaDataRemoved("key")
runCurrent()
// Media active now returns false.
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
}
}
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
new file mode 100644
index 0000000..a877853
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+xmlns:app="http://schemas.android.com/apk/res-auto"
+xmlns:tools="http://schemas.android.com/tools"
+android:layout_width="match_parent"
+android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/logo"
+ android:layout_width="@dimen/biometric_auth_icon_size"
+ android:layout_height="@dimen/biometric_auth_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitXY"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/biometric_dialog_empty_space_description"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <View
+ android:id="@+id/panel"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="?android:attr/colorBackgroundFloating"
+ android:clickable="true"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp"
+ android:visibility="visible"
+ app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+ app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
+ app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+ app:layout_constraintTop_toTopOf="@+id/title" />
+
+ <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+ android:id="@+id/biometric_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.8"
+ tools:srcCompat="@tools:sample/avatars" />
+
+ <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+ android:id="@+id/biometric_icon_overlay"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY"
+ app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
+ app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toStartOf="@+id/biometric_icon"
+ app:layout_constraintTop_toTopOf="@+id/biometric_icon"
+ app:layout_constraintVertical_bias="0.0" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="1"
+ android:ellipsize="marquee"
+ style="@style/TextAppearance.AuthCredential.Title"
+ app:layout_constraintBottom_toTopOf="@+id/subtitle"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="1"
+ android:ellipsize="marquee"
+ style="@style/TextAppearance.AuthCredential.Subtitle"
+ app:layout_constraintBottom_toTopOf="@+id/description"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <Space
+ android:id="@+id/space_above_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/biometric_prompt_space_above_content"
+ android:visibility="gone"
+ app:layout_constraintTop_toBottomOf="@+id/subtitle"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+ <ScrollView
+ android:id="@+id/customized_view_container"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:fillViewport="true"
+ android:fadeScrollbars="false"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+ android:scrollbars="vertical"
+ android:visibility="gone"
+ app:layout_constraintTop_toBottomOf="@+id/space_above_content"
+ app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ android:scrollbars="vertical"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ style="@style/TextAppearance.AuthCredential.Description"
+ app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <TextView
+ android:id="@+id/indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:gravity="center_horizontal"
+ android:textColor="@color/biometric_dialog_gray"
+ android:textSize="12sp"
+ android:accessibilityLiveRegion="polite"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:scrollHorizontally="true"
+ android:fadingEdge="horizontal"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toStartOf="@+id/panel"
+ app:layout_constraintTop_toBottomOf="@+id/biometric_icon" />
+
+ <!-- Negative Button, reserved for app -->
+ <Button
+ android:id="@+id/button_negative"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginLeft="8dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <!-- Cancel Button, replaces negative button when biometric is accepted -->
+ <Button
+ android:id="@+id/button_cancel"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginLeft="8dp"
+ android:text="@string/cancel"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <!-- "Use Credential" Button, replaces if device credential is allowed -->
+ <Button
+ android:id="@+id/button_use_credential"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginLeft="8dp"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <!-- Positive Button -->
+ <Button
+ android:id="@+id/button_confirm"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginRight="8dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_confirm"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ tools:visibility="invisible" />
+
+ <!-- Try Again Button -->
+ <Button
+ android:id="@+id/button_try_again"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginRight="8dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_try_again"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintEnd_toEndOf="@+id/panel" />
+
+ <!-- Guidelines for setting panel border -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/leftGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/rightGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/bottomGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/values-xxhdpi/dimens.xml b/packages/SystemUI/res/values-xxhdpi/dimens.xml
index 26c8437..9bff422 100644
--- a/packages/SystemUI/res/values-xxhdpi/dimens.xml
+++ b/packages/SystemUI/res/values-xxhdpi/dimens.xml
@@ -22,4 +22,8 @@
fraction of a pixel.-->
<fraction name="battery_subpixel_smoothing_left">33%</fraction>
<fraction name="battery_subpixel_smoothing_right">33%</fraction>
+
+ <!-- Biometrics fingerprint icon size for full resolution.-->
+ <dimen name="biometric_dialog_fingerprint_icon_width">120dp</dimen>
+ <dimen name="biometric_dialog_fingerprint_icon_height">120dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index dfeb1f3..d821f19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,7 +33,7 @@
public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
protected View mEcaView;
- // To avoid accidental lockout due to events while the device in in the pocket, ignore
+ // To avoid accidental lockout due to events while the device in the pocket, ignore
// any passwords with length less than or equal to this length.
protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
private KeyDownListener mKeyDownListener;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index bc12aee..ce03072 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -132,7 +132,7 @@
boolean shouldSubtleWindowAnimationsForUnlock();
/**
- * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
+ * Starts the animation before we dismiss Keyguard, i.e. a disappearing animation on the
* security view of the bouncer.
*
* @param finishRunnable the runnable to be run after the animation finished, or {@code null} if
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 5f5cca8..e8499d3 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,15 +17,11 @@
package com.android.systemui
import android.content.Context
-import android.content.res.Resources
import android.graphics.Path
import android.graphics.Rect
-import android.graphics.RectF
import android.hardware.camera2.CameraManager
-import android.util.PathParser
import com.android.systemui.res.R
import java.util.concurrent.Executor
-import kotlin.math.roundToInt
/**
* Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
@@ -163,89 +159,20 @@
}
companion object Factory {
- fun build(context: Context, executor: Executor): CameraAvailabilityListener {
+ fun build(
+ context: Context,
+ executor: Executor,
+ cameraProtectionLoader: CameraProtectionLoader
+ ): CameraAvailabilityListener {
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val res = context.resources
- val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
+ val cameraProtectionInfoList = cameraProtectionLoader.loadCameraProtectionInfoList()
val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor)
}
-
- private fun pathFromString(pathString: String): Path {
- val spec = pathString.trim()
- val p: Path
- try {
- p = PathParser.createPathFromPathData(spec)
- } catch (e: Throwable) {
- throw IllegalArgumentException("Invalid protection path", e)
- }
-
- return p
- }
-
- private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
- val list = mutableListOf<CameraProtectionInfo>()
- val front =
- loadCameraProtectionInfo(
- res,
- R.string.config_protectedCameraId,
- R.string.config_protectedPhysicalCameraId,
- R.string.config_frontBuiltInDisplayCutoutProtection
- )
- if (front != null) {
- list.add(front)
- }
- val inner =
- loadCameraProtectionInfo(
- res,
- R.string.config_protectedInnerCameraId,
- R.string.config_protectedInnerPhysicalCameraId,
- R.string.config_innerBuiltInDisplayCutoutProtection
- )
- if (inner != null) {
- list.add(inner)
- }
- return list
- }
-
- private fun loadCameraProtectionInfo(
- res: Resources,
- cameraIdRes: Int,
- physicalCameraIdRes: Int,
- pathRes: Int
- ): CameraProtectionInfo? {
- val logicalCameraId = res.getString(cameraIdRes)
- if (logicalCameraId.isNullOrEmpty()) {
- return null
- }
- val physicalCameraId = res.getString(physicalCameraIdRes)
- val protectionPath = pathFromString(res.getString(pathRes))
- val computed = RectF()
- protectionPath.computeBounds(computed)
- val protectionBounds =
- Rect(
- computed.left.roundToInt(),
- computed.top.roundToInt(),
- computed.right.roundToInt(),
- computed.bottom.roundToInt()
- )
- return CameraProtectionInfo(
- logicalCameraId,
- physicalCameraId,
- protectionPath,
- protectionBounds
- )
- }
}
- data class CameraProtectionInfo(
- val logicalCameraId: String,
- val physicalCameraId: String?,
- val cutoutProtectionPath: Path,
- val cutoutBounds: Rect,
- )
-
private data class OpenCameraInfo(
val logicalCameraId: String,
val packageId: String,
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
new file mode 100644
index 0000000..bbab4de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.graphics.Path
+import android.graphics.Rect
+
+data class CameraProtectionInfo(
+ val logicalCameraId: String,
+ val physicalCameraId: String?,
+ val cutoutProtectionPath: Path,
+ val cutoutBounds: Rect,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
new file mode 100644
index 0000000..8fe9389
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.content.Context
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.util.PathParser
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.roundToInt
+
+class CameraProtectionLoader @Inject constructor(private val context: Context) {
+
+ fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
+ val list = mutableListOf<CameraProtectionInfo>()
+ val front =
+ loadCameraProtectionInfo(
+ R.string.config_protectedCameraId,
+ R.string.config_protectedPhysicalCameraId,
+ R.string.config_frontBuiltInDisplayCutoutProtection
+ )
+ if (front != null) {
+ list.add(front)
+ }
+ val inner =
+ loadCameraProtectionInfo(
+ R.string.config_protectedInnerCameraId,
+ R.string.config_protectedInnerPhysicalCameraId,
+ R.string.config_innerBuiltInDisplayCutoutProtection
+ )
+ if (inner != null) {
+ list.add(inner)
+ }
+ return list
+ }
+
+ private fun loadCameraProtectionInfo(
+ cameraIdRes: Int,
+ physicalCameraIdRes: Int,
+ pathRes: Int
+ ): CameraProtectionInfo? {
+ val logicalCameraId = context.getString(cameraIdRes)
+ if (logicalCameraId.isNullOrEmpty()) {
+ return null
+ }
+ val physicalCameraId = context.getString(physicalCameraIdRes)
+ val protectionPath = pathFromString(context.getString(pathRes))
+ val computed = RectF()
+ protectionPath.computeBounds(computed)
+ val protectionBounds =
+ Rect(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt()
+ )
+ return CameraProtectionInfo(
+ logicalCameraId,
+ physicalCameraId,
+ protectionPath,
+ protectionBounds
+ )
+ }
+
+ private fun pathFromString(pathString: String): Path {
+ return try {
+ PathParser.createPathFromPathData(pathString.trim())
+ } catch (e: Throwable) {
+ throw IllegalArgumentException("Invalid protection path", e)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index d6d5c26..3e03fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -146,6 +146,7 @@
private final ThreadFactory mThreadFactory;
private final DecorProviderFactory mDotFactory;
private final FaceScanningProviderFactory mFaceScanningFactory;
+ private final CameraProtectionLoader mCameraProtectionLoader;
public final int mFaceScanningViewId;
@VisibleForTesting
@@ -333,7 +334,8 @@
FaceScanningProviderFactory faceScanningFactory,
ScreenDecorationsLogger logger,
FacePropertyRepository facePropertyRepository,
- JavaAdapter javaAdapter) {
+ JavaAdapter javaAdapter,
+ CameraProtectionLoader cameraProtectionLoader) {
mContext = context;
mSecureSettings = secureSettings;
mCommandRegistry = commandRegistry;
@@ -343,6 +345,7 @@
mThreadFactory = threadFactory;
mDotFactory = dotFactory;
mFaceScanningFactory = faceScanningFactory;
+ mCameraProtectionLoader = cameraProtectionLoader;
mFaceScanningViewId = com.android.systemui.res.R.id.face_scanning_anim;
mLogger = logger;
mFacePropertyRepository = facePropertyRepository;
@@ -981,7 +984,9 @@
Resources res = mContext.getResources();
boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection);
if (enabled) {
- mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mExecutor);
+ mCameraListener =
+ CameraAvailabilityListener.Factory.build(
+ mContext, mExecutor, mCameraProtectionLoader);
mCameraListener.addTransitionCallback(mCameraTransitionCallback);
mCameraListener.startListening();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 57e308f..3397906 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+import static com.android.systemui.Flags.constraintBp;
import android.animation.Animator;
import android.annotation.IntDef;
@@ -57,6 +58,7 @@
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -153,7 +155,7 @@
@Nullable private Spaghetti mBiometricView;
@Nullable private View mCredentialView;
private final AuthPanelController mPanelController;
- private final FrameLayout mFrameLayout;
+ private final ViewGroup mLayout;
private final ImageView mBackgroundView;
private final ScrollView mBiometricScrollView;
private final View mPanelView;
@@ -339,11 +341,16 @@
mBiometricCallback = new BiometricCallback();
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
- mFrameLayout = (FrameLayout) layoutInflater.inflate(
- R.layout.auth_container_view, this, false /* attachToRoot */);
- addView(mFrameLayout);
- mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
- mBackgroundView = mFrameLayout.findViewById(R.id.background);
+ if (constraintBp()) {
+ mLayout = (ConstraintLayout) layoutInflater.inflate(
+ R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
+ } else {
+ mLayout = (FrameLayout) layoutInflater.inflate(
+ R.layout.auth_container_view, this, false /* attachToRoot */);
+ }
+ mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview);
+ addView(mLayout);
+ mBackgroundView = mLayout.findViewById(R.id.background);
ViewCompat.setAccessibilityDelegate(mBackgroundView, new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
@@ -358,7 +365,7 @@
}
});
- mPanelView = mFrameLayout.findViewById(R.id.panel);
+ mPanelView = mLayout.findViewById(R.id.panel);
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
@@ -402,20 +409,31 @@
new BiometricModalities(fpProps, faceProps),
config.mOpPackageName);
- final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
- R.layout.biometric_prompt_layout, null, false);
- mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
- // TODO(b/201510778): This uses the wrong timeout in some cases
- getJankListener(view, TRANSIT,
- BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
- mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
- vibratorHelper);
+ if (constraintBp()) {
+ mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
+ // TODO(b/201510778): This uses the wrong timeout in some cases
+ getJankListener(mLayout, TRANSIT,
+ BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+ mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+ vibratorHelper);
+ } else {
+ final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
+ R.layout.biometric_prompt_layout, null, false);
+ mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
+ // TODO(b/201510778): This uses the wrong timeout in some cases
+ getJankListener(view, TRANSIT,
+ BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+ mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+ vibratorHelper);
- // TODO(b/251476085): migrate these dependencies
- if (fpProps != null && fpProps.isAnyUdfpsType()) {
- view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
- config.mScaleProvider);
+ // TODO(b/251476085): migrate these dependencies
+ if (fpProps != null && fpProps.isAnyUdfpsType()) {
+ view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
+ config.mScaleProvider);
+ }
}
+ } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
+ addCredentialView(true, false);
} else {
mPromptSelectorInteractorProvider.get().resetPrompt();
}
@@ -477,7 +495,7 @@
vm.setAnimateContents(animateContents);
((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
- mFrameLayout.addView(mCredentialView);
+ mLayout.addView(mCredentialView);
}
@Override
@@ -488,7 +506,9 @@
@Override
public void onOrientationChanged() {
- maybeUpdatePositionForUdfps(true /* invalidate */);
+ if (!constraintBp()) {
+ maybeUpdatePositionForUdfps(true /* invalidate */);
+ }
}
@Override
@@ -502,8 +522,9 @@
mWakefulnessLifecycle.addObserver(this);
mPanelInteractionDetector.enable(
() -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
-
- if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
+ if (constraintBp()) {
+ // Do nothing on attachment with constraintLayout
+ } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
mBiometricScrollView.addView(mBiometricView.asView());
} else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
addCredentialView(true /* animatePanel */, false /* animateContents */);
@@ -512,7 +533,9 @@
+ mConfig.mPromptInfo.getAuthenticators());
}
- maybeUpdatePositionForUdfps(false /* invalidate */);
+ if (!constraintBp()) {
+ maybeUpdatePositionForUdfps(false /* invalidate */);
+ }
if (mConfig.mSkipIntro) {
mContainerState = STATE_SHOWING;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 285ab4a..efad21b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -41,6 +41,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieCompositionFactory
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -70,9 +71,9 @@
@SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
- view: BiometricPromptLayout,
+ view: View,
viewModel: PromptViewModel,
- panelViewController: AuthPanelController,
+ panelViewController: AuthPanelController?,
jankListener: BiometricJankListener,
backgroundView: View,
legacyCallback: Spaghetti.Callback,
@@ -112,11 +113,18 @@
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
+ val iconSizeOverride =
+ if (constraintBp()) {
+ viewModel.fingerprintAffordanceSize
+ } else {
+ (view as BiometricPromptLayout).updatedFingerprintAffordanceSize
+ }
+
PromptIconViewBinder.bind(
iconView,
iconOverlayView,
- view.getUpdatedFingerprintAffordanceSize(),
- viewModel
+ iconSizeOverride,
+ viewModel,
)
val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index d5695f3..2417fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,29 +19,45 @@
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
+import android.graphics.Outline
+import android.graphics.Rect
+import android.transition.AutoTransition
+import android.transition.TransitionManager
import android.view.Surface
import android.view.View
import android.view.ViewGroup
+import android.view.ViewOutlineProvider
import android.view.WindowInsets
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.Guideline
import androidx.core.animation.addListener
+import androidx.core.view.doOnAttach
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.biometrics.ui.viewmodel.isBottom
import com.android.systemui.biometrics.ui.viewmodel.isLarge
+import com.android.systemui.biometrics.ui.viewmodel.isLeft
import com.android.systemui.biometrics.ui.viewmodel.isMedium
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
+import com.android.systemui.biometrics.ui.viewmodel.isRight
import com.android.systemui.biometrics.ui.viewmodel.isSmall
+import com.android.systemui.biometrics.ui.viewmodel.isTop
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
+import kotlin.math.abs
+import kotlin.math.min
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -54,18 +70,19 @@
/** Resizes [BiometricPromptLayout] and the [panelViewController] via the [PromptViewModel]. */
fun bind(
- view: BiometricPromptLayout,
+ view: View,
viewModel: PromptViewModel,
viewsToHideWhenSmall: List<View>,
viewsToFadeInOnSizeChange: List<View>,
- panelViewController: AuthPanelController,
+ panelViewController: AuthPanelController?,
jankListener: BiometricJankListener,
) {
val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
val accessibilityManager =
requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
+
fun notifyAccessibilityChanged() {
- Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
+ Utils.notifyAccessibilityContentChanged(accessibilityManager, view as ViewGroup)
}
fun startMonitoredAnimation(animators: List<Animator>) {
@@ -77,149 +94,342 @@
}
}
- val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
- val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
- val fullSizeYOffset =
- view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
+ if (constraintBp()) {
+ val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
+ val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
+ val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
- // cache the original position of the icon view (as done in legacy view)
- // this must happen before any size changes can be made
- view.doOnLayout {
- // TODO(b/251476085): this old way of positioning has proven itself unreliable
- // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
- // pin to the physical sensor
- val iconHolderOriginalY = iconHolderView.y
+ val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
+ val panelView = view.requireViewById<View>(R.id.panel)
+ val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
- // bind to prompt
- // TODO(b/251476085): migrate the legacy panel controller and simplify this
- view.repeatWhenAttached {
- var currentSize: PromptSize? = null
- lifecycleScope.launch {
- /**
- * View is only set visible in BiometricViewSizeBinder once PromptSize is
- * determined that accounts for iconView size, to prevent prompt resizing being
- * visible to the user.
- *
- * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
- * layout is implemented
- */
- combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
- (isIconViewLoaded, size) ->
- if (!isIconViewLoaded) {
- return@collect
+ // ConstraintSets for animating between prompt sizes
+ val mediumConstraintSet = ConstraintSet()
+ mediumConstraintSet.clone(view as ConstraintLayout)
+
+ val smallConstraintSet = ConstraintSet()
+ smallConstraintSet.clone(mediumConstraintSet)
+ viewsToHideWhenSmall.forEach { smallConstraintSet.setVisibility(it.id, View.GONE) }
+
+ val largeConstraintSet = ConstraintSet()
+ largeConstraintSet.clone(mediumConstraintSet)
+ viewsToHideWhenSmall.forEach { largeConstraintSet.setVisibility(it.id, View.GONE) }
+ largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+ largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+ largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+ largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0)
+ largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0)
+ largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0)
+
+ // Round the panel outline
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
+ }
+ }
+
+ view.doOnLayout {
+ val windowBounds = windowManager.maximumWindowMetrics.bounds
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
+
+ fun measureBounds(position: PromptPosition) {
+ val width = min(windowBounds.height(), windowBounds.width())
+
+ var left = -1
+ var top = -1
+ var right = -1
+ var bottom = -1
+
+ when {
+ position.isTop -> {
+ left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ top = viewModel.promptMargin
+ right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
+ }
+ position.isBottom -> {
+ if (view.isLandscape()) {
+ left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ top = iconHolderView.centerY()
+ right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ bottom = bottomInset + viewModel.promptMargin
+ } else {
+ left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ top =
+ windowBounds.height() -
+ (windowBounds.height() - iconHolderView.centerY()) * 2 +
+ viewModel.promptMargin
+ right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ bottom = viewModel.promptMargin
+ }
}
- // prepare for animated size transitions
- for (v in viewsToHideWhenSmall) {
- v.showContentOrHide(forceHide = size.isSmall)
+ // For Udfps exclusive left and right, measure guideline to center
+ // icon in BP
+ position.isLeft -> {
+ left = viewModel.promptMargin
+ top =
+ windowBounds.height() -
+ (windowBounds.height() - iconHolderView.centerY()) * 2 +
+ viewModel.promptMargin
+ right =
+ abs(
+ windowBounds.width() - iconHolderView.centerX() * 2 +
+ viewModel.promptMargin
+ )
+ bottom = bottomInset + viewModel.promptMargin
}
- if (currentSize == null && size.isSmall) {
- iconHolderView.alpha = 0f
+ position.isRight -> {
+ left =
+ abs(
+ iconHolderView.centerX() -
+ (windowBounds.width() - iconHolderView.centerX()) -
+ viewModel.promptMargin
+ )
+ top =
+ windowBounds.height() -
+ (windowBounds.height() - iconHolderView.centerY()) * 2 +
+ viewModel.promptMargin
+ right = viewModel.promptMargin
+ bottom = bottomInset + viewModel.promptMargin
}
- if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
- viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
- }
+ }
- // TODO(b/302735104): Fix wrong height due to the delay of
- // PromptContentView. addOnLayoutChangeListener() will cause crash when
- // showing credential view, since |PromptIconViewModel| won't release the
- // flow.
- // propagate size changes to legacy panel controller and animate transitions
- view.doOnLayout {
- val width = view.measuredWidth
- val height = view.measuredHeight
+ val bounds = Rect(left, top, right, bottom)
+ if (bounds.shouldAdjustLeftGuideline()) {
+ leftGuideline.setGuidelineBegin(bounds.left)
+ smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ }
+ if (bounds.shouldAdjustRightGuideline()) {
+ rightGuideline.setGuidelineEnd(bounds.right)
+ smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ }
+ if (bounds.shouldAdjustBottomGuideline()) {
+ bottomGuideline.setGuidelineEnd(bounds.bottom)
+ smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+ mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+ }
+ }
- when {
- size.isSmall -> {
- iconHolderView.alpha = 1f
- val bottomInset =
- windowManager.maximumWindowMetrics.windowInsets
- .getInsets(WindowInsets.Type.navigationBars())
- .bottom
- iconHolderView.y =
- if (view.isLandscape()) {
- (view.height - iconHolderView.height - bottomInset) / 2f
- } else {
- view.height -
- iconHolderView.height -
- iconPadding -
- bottomInset
- }
- val newHeight =
- iconHolderView.height + (2 * iconPadding.toInt()) -
- iconHolderView.paddingTop -
- iconHolderView.paddingBottom
- panelViewController.updateForContentDimensions(
- width,
- newHeight + bottomInset,
- 0, /* animateDurationMs */
- )
- }
- size.isMedium && currentSize.isSmall -> {
- val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
- panelViewController.updateForContentDimensions(
- width,
- height,
- duration,
- )
- startMonitoredAnimation(
- listOf(
- iconHolderView.asVerticalAnimator(
- duration = duration.toLong(),
- toY =
- iconHolderOriginalY -
- viewsToHideWhenSmall
- .filter { it.isGone }
- .sumOf { it.height },
- ),
- viewsToFadeInOnSizeChange.asFadeInAnimator(
- duration = duration.toLong(),
- delay = duration.toLong(),
- ),
+ view.repeatWhenAttached {
+ var currentSize: PromptSize? = null
+ lifecycleScope.launch {
+ combine(viewModel.position, viewModel.size, ::Pair).collect {
+ (position, size) ->
+ view.doOnAttach {
+ measureBounds(position)
+
+ when {
+ size.isSmall -> {
+ val ratio =
+ if (view.isLandscape()) {
+ (windowBounds.height() -
+ bottomInset -
+ viewModel.promptMargin)
+ .toFloat() / windowBounds.height()
+ } else {
+ (windowBounds.height() - viewModel.promptMargin)
+ .toFloat() / windowBounds.height()
+ }
+ smallConstraintSet.setVerticalBias(iconHolderView.id, ratio)
+
+ smallConstraintSet.applyTo(view as ConstraintLayout?)
+ }
+ size.isMedium && currentSize.isSmall -> {
+ val autoTransition = AutoTransition()
+ autoTransition.setDuration(
+ ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
)
- )
- }
- size.isMedium && currentSize.isNullOrNotSmall -> {
- panelViewController.updateForContentDimensions(
- width,
- height,
- 0, /* animateDurationMs */
- )
- }
- size.isLarge -> {
- val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
- panelViewController.setUseFullScreen(true)
- panelViewController.updateForContentDimensions(
- panelViewController.containerWidth,
- panelViewController.containerHeight,
- duration,
- )
- startMonitoredAnimation(
- listOf(
- view.asVerticalAnimator(
- duration.toLong() * 2 / 3,
- toY = view.y - fullSizeYOffset
- ),
- listOf(view)
- .asFadeInAnimator(
- duration = duration.toLong() / 2,
- delay = duration.toLong(),
- ),
+ TransitionManager.beginDelayedTransition(
+ view,
+ autoTransition
)
- )
- // TODO(b/251476085): clean up (copied from legacy)
- if (view.isAttachedToWindow) {
- val parent = view.parent as? ViewGroup
- parent?.removeView(view)
+ mediumConstraintSet.applyTo(view)
+ }
+ size.isLarge -> {
+ val autoTransition = AutoTransition()
+ autoTransition.setDuration(
+ ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
+ )
+
+ TransitionManager.beginDelayedTransition(
+ view,
+ autoTransition
+ )
+ largeConstraintSet.applyTo(view)
}
}
+
+ currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
+ notifyAccessibilityChanged()
+
+ view.invalidate()
+ view.requestLayout()
+ }
+ }
+ }
+ }
+ }
+ } else if (panelViewController != null) {
+ val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
+ val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
+ val fullSizeYOffset =
+ view.resources.getDimension(
+ R.dimen.biometric_dialog_medium_to_large_translation_offset
+ )
+
+ // cache the original position of the icon view (as done in legacy view)
+ // this must happen before any size changes can be made
+ view.doOnLayout {
+ // TODO(b/251476085): this old way of positioning has proven itself unreliable
+ // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+ // pin to the physical sensor
+ val iconHolderOriginalY = iconHolderView.y
+
+ // bind to prompt
+ // TODO(b/251476085): migrate the legacy panel controller and simplify this
+ view.repeatWhenAttached {
+ var currentSize: PromptSize? = null
+ lifecycleScope.launch {
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is
+ * determined that accounts for iconView size, to prevent prompt resizing
+ * being visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+ * layout is implemented
+ */
+ combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+ (isIconViewLoaded, size) ->
+ if (!isIconViewLoaded) {
+ return@collect
}
- currentSize = size
- view.visibility = View.VISIBLE
- viewModel.setIsIconViewLoaded(false)
- notifyAccessibilityChanged()
+ // prepare for animated size transitions
+ for (v in viewsToHideWhenSmall) {
+ v.showContentOrHide(forceHide = size.isSmall)
+ }
+ if (currentSize == null && size.isSmall) {
+ iconHolderView.alpha = 0f
+ }
+ if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
+ viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
+ }
+
+ // TODO(b/302735104): Fix wrong height due to the delay of
+ // PromptContentView. addOnLayoutChangeListener() will cause crash when
+ // showing credential view, since |PromptIconViewModel| won't release
+ // the
+ // flow.
+ // propagate size changes to legacy panel controller and animate
+ // transitions
+ view.doOnLayout {
+ val width = view.measuredWidth
+ val height = view.measuredHeight
+
+ when {
+ size.isSmall -> {
+ iconHolderView.alpha = 1f
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
+ iconHolderView.y =
+ if (view.isLandscape()) {
+ (view.height -
+ iconHolderView.height -
+ bottomInset) / 2f
+ } else {
+ view.height -
+ iconHolderView.height -
+ iconPadding -
+ bottomInset
+ }
+ val newHeight =
+ iconHolderView.height + (2 * iconPadding.toInt()) -
+ iconHolderView.paddingTop -
+ iconHolderView.paddingBottom
+ panelViewController.updateForContentDimensions(
+ width,
+ newHeight + bottomInset,
+ 0, /* animateDurationMs */
+ )
+ }
+ size.isMedium && currentSize.isSmall -> {
+ val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
+ panelViewController.updateForContentDimensions(
+ width,
+ height,
+ duration,
+ )
+ startMonitoredAnimation(
+ listOf(
+ iconHolderView.asVerticalAnimator(
+ duration = duration.toLong(),
+ toY =
+ iconHolderOriginalY -
+ viewsToHideWhenSmall
+ .filter { it.isGone }
+ .sumOf { it.height },
+ ),
+ viewsToFadeInOnSizeChange.asFadeInAnimator(
+ duration = duration.toLong(),
+ delay = duration.toLong(),
+ ),
+ )
+ )
+ }
+ size.isMedium && currentSize.isNullOrNotSmall -> {
+ panelViewController.updateForContentDimensions(
+ width,
+ height,
+ 0, /* animateDurationMs */
+ )
+ }
+ size.isLarge -> {
+ val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
+ panelViewController.setUseFullScreen(true)
+ panelViewController.updateForContentDimensions(
+ panelViewController.containerWidth,
+ panelViewController.containerHeight,
+ duration,
+ )
+
+ startMonitoredAnimation(
+ listOf(
+ view.asVerticalAnimator(
+ duration.toLong() * 2 / 3,
+ toY = view.y - fullSizeYOffset
+ ),
+ listOf(view)
+ .asFadeInAnimator(
+ duration = duration.toLong() / 2,
+ delay = duration.toLong(),
+ ),
+ )
+ )
+ // TODO(b/251476085): clean up (copied from legacy)
+ if (view.isAttachedToWindow) {
+ val parent = view.parent as? ViewGroup
+ parent?.removeView(view)
+ }
+ }
+ }
+
+ currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
+ notifyAccessibilityChanged()
+ }
}
}
}
@@ -244,6 +454,20 @@
}
}
+private fun View.centerX(): Int {
+ return (x + width / 2).toInt()
+}
+
+private fun View.centerY(): Int {
+ return (y + height / 2).toInt()
+}
+
+private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1
+
+private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1
+
+private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1
+
private fun View.asVerticalAnimator(
duration: Long,
toY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 6e3bcf5..2e47375 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -17,13 +17,17 @@
package com.android.systemui.biometrics.ui.binder
+import android.graphics.Rect
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
@@ -119,6 +123,24 @@
}
launch {
+ viewModel.iconPosition.collect { position ->
+ if (constraintBp() && position != Rect()) {
+ val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams
+
+ if (position.left != -1) {
+ iconParams.endToEnd = ConstraintSet.UNSET
+ iconParams.leftMargin = position.left
+ }
+ if (position.top != -1) {
+ iconParams.bottomToBottom = ConstraintSet.UNSET
+ iconParams.topMargin = position.top
+ }
+ iconView.layoutParams = iconParams
+ }
+ }
+ }
+
+ launch {
viewModel.iconAsset
.sample(
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 3defec5..b7cffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -20,8 +20,11 @@
import android.annotation.DrawableRes
import android.annotation.RawRes
import android.content.res.Configuration
+import android.graphics.Rect
+import android.util.RotationUtils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.res.R
@@ -42,7 +45,8 @@
constructor(
promptViewModel: PromptViewModel,
private val displayStateInteractor: DisplayStateInteractor,
- promptSelectorInteractor: PromptSelectorInteractor
+ promptSelectorInteractor: PromptSelectorInteractor,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
/** Auth types for the UI to display. */
@@ -71,7 +75,40 @@
} else if (modalities.hasFingerprintOnly) {
AuthType.Fingerprint
} else {
- throw IllegalStateException("unexpected modality: $modalities")
+ // TODO(b/288175072): Remove, currently needed for transition to credential view
+ AuthType.Fingerprint
+ }
+ }
+
+ val udfpsSensorBounds: Flow<Rect> =
+ combine(
+ udfpsOverlayInteractor.udfpsOverlayParams,
+ displayStateInteractor.currentRotation
+ ) { params, rotation ->
+ val rotatedBounds = Rect(params.sensorBounds)
+ RotationUtils.rotateBounds(
+ rotatedBounds,
+ params.naturalDisplayWidth,
+ params.naturalDisplayHeight,
+ rotation.ordinal
+ )
+ rotatedBounds
+ }
+ .distinctUntilChanged()
+
+ val iconPosition: Flow<Rect> =
+ combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) {
+ sensorBounds,
+ size,
+ modalities ->
+ // If not Udfps, icon does not change from default layout position
+ if (!modalities.hasUdfps) {
+ Rect() // Empty rect, don't offset from default position
+ } else if (size.isSmall) {
+ // When small with Udfps, only set horizontal position
+ Rect(sensorBounds.left, -1, sensorBounds.right, -1)
+ } else {
+ sensorBounds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt
new file mode 100644
index 0000000..d45dad6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+/** The position of a biometric prompt */
+enum class PromptPosition {
+ Top,
+ Bottom,
+ Left,
+ Right,
+}
+
+val PromptPosition?.isBottom: Boolean
+ get() = this != null && this == PromptPosition.Bottom
+
+val PromptPosition?.isLeft: Boolean
+ get() = this != null && this == PromptPosition.Left
+
+val PromptPosition?.isRight: Boolean
+ get() = this != null && this == PromptPosition.Right
+
+val PromptPosition?.isTop: Boolean
+ get() = this != null && this == PromptPosition.Top
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index a39aabf..ef5c37ea 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -82,11 +82,23 @@
val faceIconHeight: Int =
context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ val fingerprintSensorDiameter: Int =
+ (udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.width() *
+ udfpsOverlayInteractor.udfpsOverlayParams.value.scaleFactor)
+ .toInt()
+ val fingerprintAffordanceSize: Pair<Int, Int>? =
+ if (fingerprintSensorDiameter != 0)
+ Pair(fingerprintSensorDiameter, fingerprintSensorDiameter)
+ else null
+
private val _accessibilityHint = MutableSharedFlow<String>()
/** Hint for talkback directional guidance */
val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
+ val promptMargin: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
+
private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -136,6 +148,22 @@
/** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
val hapticsToPlay = _hapticsToPlay.asStateFlow()
+ /** The current position of the prompt */
+ val position: Flow<PromptPosition> =
+ combine(_forceLargeSize, modalities, displayStateInteractor.currentRotation) {
+ forceLarge,
+ modalities,
+ rotation ->
+ when {
+ forceLarge || !modalities.hasUdfps -> PromptPosition.Bottom
+ rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
+ rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
+ rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top
+ else -> PromptPosition.Bottom
+ }
+ }
+ .distinctUntilChanged()
+
/** The size of the prompt. */
val size: Flow<PromptSize> =
combine(
@@ -196,7 +224,12 @@
.distinctUntilChanged()
val iconViewModel: PromptIconViewModel =
- PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+ PromptIconViewModel(
+ this,
+ displayStateInteractor,
+ promptSelectorInteractor,
+ udfpsOverlayInteractor
+ )
private val _isIconViewLoaded = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index c46f0d1..33edb80 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -21,21 +21,21 @@
/** Data model of media on the communal hub. */
data class CommunalMediaModel(
- val hasAnyMediaOrRecommendation: Boolean,
+ val hasActiveMediaOrRecommendation: Boolean,
val createdTimestampMillis: Long = 0L,
) : Diffable<CommunalMediaModel> {
companion object {
val INACTIVE =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = false,
+ hasActiveMediaOrRecommendation = false,
)
}
override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
- if (hasAnyMediaOrRecommendation != prevVal.hasAnyMediaOrRecommendation) {
+ if (hasActiveMediaOrRecommendation != prevVal.hasActiveMediaOrRecommendation) {
row.logChange(
columnName = "isMediaActive",
- value = hasAnyMediaOrRecommendation,
+ value = hasActiveMediaOrRecommendation,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index 2b66491..201be51 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -73,10 +73,10 @@
)
private fun updateMediaModel(data: MediaData? = null) {
- if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+ if (mediaDataManager.hasActiveMediaOrRecommendation()) {
_mediaModel.value =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = true,
+ hasActiveMediaOrRecommendation = true,
createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 75a27a2..950ac3c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -318,7 +318,7 @@
)
// Add UMO
- if (media.hasAnyMediaOrRecommendation) {
+ if (media.hasActiveMediaOrRecommendation) {
ongoingContent.add(
CommunalContentModel.Umo(
createdTimestampMillis = media.createdTimestampMillis,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 0c12841..40d2d16 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -87,7 +87,7 @@
with(mediaHost) {
expansion = MediaHostState.EXPANDED
expandedMatchesParentHeight = true
- showsOnlyActiveMedia = false
+ showsOnlyActiveMedia = true
falsingProtectionNeeded = false
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 947cb02..8df7e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -17,7 +17,6 @@
package com.android.systemui.compose
-import android.app.Dialog
import android.content.Context
import android.view.View
import android.view.WindowInsets
@@ -33,7 +32,6 @@
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -96,11 +94,11 @@
sceneByKey: Map<SceneKey, Scene>,
): View
- /** Creates sticky key dialog presenting provided [viewModel] */
- fun createStickyKeysDialog(
- dialogFactory: SystemUIDialogFactory,
+ /** Creates sticky key indicator content presenting provided [viewModel] */
+ fun createStickyKeysIndicatorContent(
+ context: Context,
viewModel: StickyKeysIndicatorViewModel
- ): Dialog
+ ): View
/** Create a [View] to represent [viewModel] on screen. */
fun createCommunalView(
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index c93b8e1..1230156 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -292,7 +292,7 @@
private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
return if (DEBUG) {
- traceEach(flowName, logcat = true)
+ traceEach(flowName, logcat = true, traceEmissionCount = true)
} else {
this
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index c331164..537cacd 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -93,7 +93,11 @@
}
mDockState = dockState;
- if (isPulsing()) {
+ if (mMachine.isExecutingTransition() || isPulsing()) {
+ // If the device is in the middle of executing a transition or is pulsing,
+ // exit early instead of requesting a new state. DozeMachine
+ // will check the docked state and resolveIntermediateState in the next
+ // transition after pulse done.
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
new file mode 100644
index 0000000..3ed58a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.keyboard.stickykeys.ui
+
+import android.app.Dialog
+import android.content.Context
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import androidx.activity.ComponentDialog
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class StickyKeyDialogFactory
+@Inject
+constructor(
+ @Application val context: Context,
+) {
+
+ fun create(viewModel: StickyKeysIndicatorViewModel): Dialog {
+ return createStickyKeyIndicator(viewModel)
+ }
+
+ private fun createStickyKeyIndicator(viewModel: StickyKeysIndicatorViewModel): Dialog {
+ return ComponentDialog(context, R.style.Theme_SystemUI_Dialog).apply {
+ // because we're requesting window feature it must be called before setting content
+ window?.setStickyKeyWindowAttributes()
+ setContentView(ComposeFacade.createStickyKeysIndicatorContent(context, viewModel))
+ }
+ }
+
+ private fun Window.setStickyKeyWindowAttributes() {
+ requestFeature(Window.FEATURE_NO_TITLE)
+ setType(TYPE_STATUS_BAR_SUB_PANEL)
+ addFlags(FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCHABLE)
+ clearFlags(FLAG_DIM_BEHIND)
+ setGravity(Gravity.TOP or Gravity.END)
+ attributes =
+ WindowManager.LayoutParams().apply {
+ copyFrom(attributes)
+ title = "StickyKeysIndicator"
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
index c3a618d..842fd04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -18,16 +18,11 @@
import android.app.Dialog
import android.util.Log
-import android.view.Gravity
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.Window
-import android.view.WindowManager
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -37,7 +32,7 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val dialogFactory: SystemUIDialogFactory,
+ private val stickyKeyDialogFactory: StickyKeyDialogFactory,
private val viewModel: StickyKeysIndicatorViewModel,
private val stickyKeysLogger: StickyKeysLogger,
) {
@@ -57,25 +52,10 @@
dialog?.dismiss()
dialog = null
} else if (dialog == null) {
- dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply {
- window?.setAttributes()
- show()
- }
+ dialog = stickyKeyDialogFactory.create(viewModel)
+ dialog?.show()
}
}
}
}
-
- private fun Window.setAttributes() {
- setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
- addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
- addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
- clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
- setGravity(Gravity.TOP or Gravity.END)
- attributes = WindowManager.LayoutParams().apply {
- copyFrom(attributes)
- width = WRAP_CONTENT
- title = "StickyKeysIndicator"
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 9b83b75..ee3706a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -80,7 +80,7 @@
}
/**
- * Click listener for messsage.
+ * Click listener for message.
*/
public @Nullable View.OnClickListener getClickListener() {
return mOnClickListener;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f085e88..4766a84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -444,7 +444,7 @@
/**
* Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
* called.
- * */
+ */
private boolean mHiding;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 7f8be1c..ef50265 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -14,6 +14,7 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.ExpandHelper
+import com.android.systemui.Flags.nsslFalsingFix
import com.android.systemui.Gefingerpoken
import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy
import com.android.systemui.classifier.Classifier
@@ -889,7 +890,7 @@
isDraggingDown = false
isTrackpadReverseScroll = false
shadeRepository.setLegacyLockscreenShadeTracking(false)
- if (KeyguardShadeMigrationNssl.isEnabled) {
+ if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled) {
return true
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index eaff8a0..a2ff406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -21,8 +21,9 @@
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.server.notification.Flags.screenshareNotificationHiding;
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.Flags.nsslFalsingFix;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -2054,7 +2055,7 @@
}
boolean horizontalSwipeWantsIt = false;
boolean scrollerWantsIt = false;
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled()) {
// Reverse the order relative to the else statement. onScrollTouch will reset on an
// UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
if (mLongPressedView == null && !mView.isBeingDragged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index e921a59..64cd526 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -344,10 +344,15 @@
}
private fun createAndStartSut(): CameraAvailabilityListener {
- return CameraAvailabilityListener.build(context, context.mainExecutor).also {
- it.addTransitionCallback(cameraTransitionCallback)
- it.startListening()
- }
+ return CameraAvailabilityListener.build(
+ context,
+ context.mainExecutor,
+ CameraProtectionLoader((context))
+ )
+ .also {
+ it.addTransitionCallback(cameraTransitionCallback)
+ it.startListening()
+ }
}
private class TestCameraTransitionCallback :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
new file mode 100644
index 0000000..238e5e9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CameraProtectionLoaderTest : SysuiTestCase() {
+
+ private val loader = CameraProtectionLoader(context)
+
+ @Before
+ fun setUp() {
+ overrideResource(R.string.config_protectedCameraId, OUTER_CAMERA_LOGICAL_ID)
+ overrideResource(R.string.config_protectedPhysicalCameraId, OUTER_CAMERA_PHYSICAL_ID)
+ overrideResource(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ OUTER_CAMERA_PROTECTION_PATH
+ )
+ overrideResource(R.string.config_protectedInnerCameraId, INNER_CAMERA_LOGICAL_ID)
+ overrideResource(R.string.config_protectedInnerPhysicalCameraId, INNER_CAMERA_PHYSICAL_ID)
+ overrideResource(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ INNER_CAMERA_PROTECTION_PATH
+ )
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList() {
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos)
+ .containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
+ overrideResource(R.string.config_protectedCameraId, "")
+
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
+ overrideResource(R.string.config_protectedInnerCameraId, "")
+
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList_innerAndOuterCameraIdsEmpty_returnsEmpty() {
+ overrideResource(R.string.config_protectedCameraId, "")
+ overrideResource(R.string.config_protectedInnerCameraId, "")
+
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos).isEmpty()
+ }
+
+ private fun CameraProtectionInfo.toTestableVersion() =
+ TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds)
+
+ /**
+ * "Testable" version, because the original version contains a Path property, which doesn't
+ * implement equals.
+ */
+ private data class TestableProtectionInfo(
+ val logicalCameraId: String,
+ val physicalCameraId: String?,
+ val cutoutBounds: Rect,
+ )
+
+ companion object {
+ private const val OUTER_CAMERA_LOGICAL_ID = "1"
+ private const val OUTER_CAMERA_PHYSICAL_ID = "11"
+ private const val OUTER_CAMERA_PROTECTION_PATH = "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+ private val OUTER_CAMERA_PROTECTION_BOUNDS =
+ Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+ private val OUTER_CAMERA_PROTECTION_INFO =
+ TestableProtectionInfo(
+ OUTER_CAMERA_LOGICAL_ID,
+ OUTER_CAMERA_PHYSICAL_ID,
+ OUTER_CAMERA_PROTECTION_BOUNDS
+ )
+
+ private const val INNER_CAMERA_LOGICAL_ID = "2"
+ private const val INNER_CAMERA_PHYSICAL_ID = "22"
+ private const val INNER_CAMERA_PROTECTION_PATH = "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+ private val INNER_CAMERA_PROTECTION_BOUNDS =
+ Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+ private val INNER_CAMERA_PROTECTION_INFO =
+ TestableProtectionInfo(
+ INNER_CAMERA_LOGICAL_ID,
+ INNER_CAMERA_PHYSICAL_ID,
+ INNER_CAMERA_PROTECTION_BOUNDS
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c07148b..1f1fa72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -176,6 +176,8 @@
private FakeFacePropertyRepository mFakeFacePropertyRepository =
new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
+ private final CameraProtectionLoader mCameraProtectionLoader =
+ new CameraProtectionLoader(mContext);
@Before
public void setup() {
@@ -247,7 +249,7 @@
mThreadFactory,
mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
- mFakeFacePropertyRepository, mJavaAdapter) {
+ mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader) {
@Override
public void start() {
super.start();
@@ -1243,7 +1245,7 @@
mDotViewController,
mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
- mFakeFacePropertyRepository, mJavaAdapter);
+ mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader);
screenDecorations.start();
when(mContext.getDisplay()).thenReturn(mDisplay);
when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index af027e8..6d2df19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -63,6 +63,7 @@
mDockHandler = new DozeDockHandler(mConfig, mDockManagerFake, mUserTracker);
mDockHandler.setDozeMachine(mMachine);
+ when(mMachine.isExecutingTransition()).thenReturn(false);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
when(mMachine.getState()).thenReturn(State.DOZE_AOD);
doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
@@ -148,4 +149,13 @@
verify(mMachine, never()).requestState(any(State.class));
}
+
+ @Test
+ public void onEvent_dockedWhileTransitioning_wontRequestStateChange() {
+ when(mMachine.isExecutingTransition()).thenReturn(true);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
+
+ verify(mMachine, never()).requestState(any(State.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
index df73cc8..a992956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.stickykeys.ui
+import android.app.Dialog
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.compose.ComposeFacade
@@ -26,8 +27,6 @@
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -40,8 +39,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
@@ -53,15 +50,13 @@
private lateinit var coordinator: StickyKeysIndicatorCoordinator
private val testScope = TestScope(StandardTestDispatcher())
private val stickyKeysRepository = FakeStickyKeysRepository()
- private val dialog = mock<ComponentSystemUIDialog>()
+ private val dialog = mock<Dialog>()
@Before
fun setup() {
Assume.assumeTrue(ComposeFacade.isComposeAvailable())
- val dialogFactory = mock<SystemUIDialogFactory> {
- whenever(applicationContext).thenReturn(context)
- whenever(create(any(), anyInt(), anyBoolean())).thenReturn(dialog)
- }
+ val dialogFactory = mock<StickyKeyDialogFactory>()
+ whenever(dialogFactory.create(any())).thenReturn(dialog)
val keyboardRepository = Kosmos().keyboardRepository
val viewModel = StickyKeysIndicatorViewModel(
stickyKeysRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 0831971..25a7eb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -16,12 +16,14 @@
package com.android.systemui.shade
+import android.content.Context
import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.MotionEvent
import android.view.View
+import android.view.WindowManager
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -33,18 +35,21 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Assert.assertThrows
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -52,12 +57,17 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@Ignore("b/323053208")
+@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class GlanceableHubContainerControllerTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos: Kosmos =
+ testKosmos().apply {
+ // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using
+ // SharedFlow
+ testDispatcher = UnconfinedTestDispatcher()
+ }
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -104,17 +114,19 @@
R.dimen.communal_bottom_edge_swipe_region_height,
BOTTOM_SWIPE_REGION_WIDTH
)
+
+ initAndAttachContainerView()
}
@Test
- fun isEnabled_interactorEnabled_interceptsTouches() {
+ fun isEnabled_communalEnabled_returnsTrue() {
communalRepository.setIsCommunalEnabled(true)
assertThat(underTest.isEnabled()).isTrue()
}
@Test
- fun isEnabled_interactorDisabled_doesNotIntercept() {
+ fun isEnabled_communalDisabled_returnsFalse() {
communalRepository.setIsCommunalEnabled(false)
assertThat(underTest.isEnabled()).isFalse()
@@ -124,11 +136,29 @@
fun initView_notEnabled_throwsException() {
communalRepository.setIsCommunalEnabled(false)
+ underTest =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalViewModel,
+ keyguardTransitionInteractor,
+ shadeInteractor,
+ powerManager,
+ )
+
assertThrows(RuntimeException::class.java) { underTest.initView(context) }
}
@Test
fun initView_calledTwice_throwsException() {
+ underTest =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalViewModel,
+ keyguardTransitionInteractor,
+ shadeInteractor,
+ powerManager,
+ )
+
// First call succeeds.
underTest.initView(context)
@@ -137,25 +167,20 @@
}
@Test
- fun onTouchEvent_touchInsideGestureRegion_interceptsTouches() {
- // Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+ fun onTouchEvent_communalClosed_doesNotIntercept() {
+ // Communal is closed.
+ goToScene(CommunalSceneKey.Blank)
- initAndAttachContainerView()
-
- // Touch events are intercepted.
- assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
@Test
- fun onTouchEvent_subsequentTouchesAfterGestureStart_interceptsTouches() {
- // Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+ fun onTouchEvent_openGesture_interceptsTouches() {
+ // Communal is closed.
+ goToScene(CommunalSceneKey.Blank)
- initAndAttachContainerView()
-
- // Initial touch down is intercepted, and so are touches outside of the region, until an up
- // event is received.
+ // Initial touch down is intercepted, and so are touches outside of the region, until an
+ // up event is received.
assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
@@ -165,34 +190,27 @@
@Test
fun onTouchEvent_communalOpen_interceptsTouches() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+ goToScene(CommunalSceneKey.Communal)
- initAndAttachContainerView()
- testableLooper.processAllMessages()
-
- // Touch events are intercepted.
+ // Touch events are intercepted outside of any gesture areas.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
// User activity sent to PowerManager.
verify(powerManager).userActivity(any(), any(), any())
}
@Test
- fun onTouchEvent_topSwipeWhenHubOpen_returnsFalse() {
+ fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
// Touch event in the top swipe reqgion is not intercepted.
assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
}
@Test
- fun onTouchEvent_bottomSwipeWhenHubOpen_returnsFalse() {
+ fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
// Touch event in the bottom swipe reqgion is not intercepted.
assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
@@ -201,9 +219,7 @@
@Test
fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
// Bouncer is visible.
bouncerShowingFlow.value = true
@@ -218,9 +234,7 @@
@Test
fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
shadeShowingFlow.value = true
testableLooper.processAllMessages()
@@ -232,10 +246,7 @@
@Test
fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
- testableLooper.processAllMessages()
+ goToScene(CommunalSceneKey.Communal)
// Touch events are intercepted.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
@@ -253,15 +264,24 @@
parentView = FrameLayout(context)
parentView.addView(containerView)
- // Make view clickable so that dispatchTouchEvent returns true.
- containerView.isClickable = true
-
underTest.initView(containerView)
+
// Attach the view so that flows start collecting.
ViewUtils.attachView(parentView)
- // Give the view a size so that determining if a touch starts at the right edge works.
- parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
- containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
+
+ // Give the view a fixed size to simplify testing for edge swipes.
+ val lp =
+ parentView.layoutParams.apply {
+ width = CONTAINER_WIDTH
+ height = CONTAINER_HEIGHT
+ }
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ wm.updateViewLayout(parentView, lp)
+ }
+
+ private fun goToScene(scene: CommunalSceneKey) {
+ communalRepository.setDesiredScene(scene)
+ testableLooper.processAllMessages()
}
companion object {
@@ -271,13 +291,17 @@
private const val TOP_SWIPE_REGION_WIDTH = 20
private const val BOTTOM_SWIPE_REGION_WIDTH = 20
+ /**
+ * A touch down event right in the middle of the screen, to avoid being in any of the swipe
+ * regions.
+ */
private val DOWN_EVENT =
MotionEvent.obtain(
0L,
0L,
MotionEvent.ACTION_DOWN,
- CONTAINER_WIDTH.toFloat(),
- CONTAINER_HEIGHT.toFloat(),
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
0
)
private val DOWN_IN_RIGHT_SWIPE_REGION_EVENT =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9ea4142..f3e9203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -616,7 +616,7 @@
mPositioner,
mock(DisplayController.class),
mOneHandedOptional,
- Optional.of(mock(DragAndDropController.class)),
+ mock(DragAndDropController.class),
syncExecutor,
mock(Handler.class),
mTaskViewTransitions,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 9ad234e1..4a5ebd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -71,7 +71,7 @@
BubblePositioner positioner,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
ShellExecutor shellMainExecutor,
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ea3ccf..1884a32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -28,7 +28,7 @@
fun mediaActive(timestamp: Long = 0L) {
_mediaModel.value =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = true,
+ hasActiveMediaOrRecommendation = true,
createdTimestampMillis = timestamp,
)
}
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
index 83fda9e..aafaaff 100644
--- a/ravenwood/bulk_enable.py
+++ b/ravenwood/bulk_enable.py
@@ -21,7 +21,7 @@
classes that have partial success.
Typical usage:
-$ ENABLE_PROBE_IGNORED=1 atest MyTestsRavenwood
+$ RAVENWOOD_RUN_DISABLED_TESTS=1 atest MyTestsRavenwood
$ cd /path/to/tests/root
$ python bulk_enable.py /path/to/atest/output/host_log.txt
"""
diff --git a/ravenwood/coretest/Android.bp b/ravenwood/coretest/Android.bp
new file mode 100644
index 0000000..9b7f8f7
--- /dev/null
+++ b/ravenwood/coretest/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+ name: "RavenwoodCoreTest",
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ ],
+
+ srcs: [
+ "test/**/*.java",
+ ],
+ sdk_version: "test_current",
+ auto_gen_config: true,
+}
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
new file mode 100644
index 0000000..e58c282
--- /dev/null
+++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood.coretest;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for the test runner validator in RavenwoodRule.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodTestRunnerValidationTest {
+ // Note the following rules don't have a @Rule, because they need to be applied in a specific
+ // order. So we use a RuleChain instead.
+ private ExpectedException mThrown = ExpectedException.none();
+ private final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Rule
+ public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
+
+ public RavenwoodTestRunnerValidationTest() {
+ Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled());
+ // Because RavenwoodRule will throw this error before executing the test method,
+ // we can't do it in the test method itself.
+ // So instead, we initialize it here.
+ mThrown.expectMessage("Switch to androidx.test.ext.junit.runners.AndroidJUnit4");
+ }
+
+ @Test
+ public void testValidateTestRunner() {
+ }
+}
diff --git a/ravenwood/fix_test_runner.py b/ravenwood/fix_test_runner.py
new file mode 100755
index 0000000..99b7a1f
--- /dev/null
+++ b/ravenwood/fix_test_runner.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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.
+
+"""
+Tool switch the deprecated jetpack test runner to the correct one.
+
+Typical usage:
+$ RAVENWOOD_OPTIONAL_VALIDATION=1 atest MyTestsRavenwood # Prepend RAVENWOOD_RUN_DISABLED_TESTS=1 as needed
+$ cd /path/to/tests/root
+$ python bulk_enable.py /path/to/atest/output/host_log.txt
+"""
+
+import collections
+import os
+import re
+import subprocess
+import sys
+
+re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+
+OLD_RUNNER = "androidx.test.runner.AndroidJUnit4"
+NEW_RUNNER = "androidx.test.ext.junit.runners.AndroidJUnit4"
+SED_ARG = r"s/%s/%s/g" % (OLD_RUNNER, NEW_RUNNER)
+
+target = collections.defaultdict()
+
+with open(sys.argv[1]) as f:
+ for line in f.readlines():
+ result = re_result.search(line)
+ if result:
+ clazz, method, state, msg = result.groups()
+ if NEW_RUNNER in msg:
+ target[clazz] = 1
+
+if len(target) == 0:
+ print("No tests need updating.")
+ sys.exit(0)
+
+num_fixed = 0
+for clazz in target.keys():
+ print("Fixing test runner", clazz)
+ clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
+ found = False
+ for root, dirs, files in os.walk("."):
+ for f in files:
+ if clazz_match.match(f):
+ found = True
+ num_fixed += 1
+ path = os.path.join(root, f)
+ subprocess.run(["sed", "-i", "-E", SED_ARG, path])
+ if not found:
+ print(f" Warining: tests {clazz} not found")
+
+
+print("Tests fixed", num_fixed)
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 8af561f..85fa65a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -28,7 +28,10 @@
import com.android.internal.os.RuntimeInit;
+import org.junit.Assert;
import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
import java.io.PrintStream;
import java.util.Map;
@@ -167,4 +170,31 @@
}
}
}
+
+ public static void validate(Statement base, Description description,
+ boolean enableOptionalValidation) {
+ validateTestRunner(base, description, enableOptionalValidation);
+ }
+
+ private static void validateTestRunner(Statement base, Description description,
+ boolean shouldFail) {
+ final var testClass = description.getTestClass();
+ final var runWith = testClass.getAnnotation(RunWith.class);
+ if (runWith == null) {
+ return;
+ }
+
+ // Due to build dependencies, we can't directly refer to androidx classes here,
+ // so just check the class name instead.
+ if (runWith.value().getCanonicalName().equals("androidx.test.runner.AndroidJUnit4")) {
+ var message = "Test " + testClass.getCanonicalName() + " uses deprecated"
+ + " test runner androidx.test.runner.AndroidJUnit4."
+ + " Switch to androidx.test.ext.junit.runners.AndroidJUnit4.";
+ if (shouldFail) {
+ Assert.fail(message);
+ } else {
+ System.err.println("Warning: " + message);
+ }
+ }
+ }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index daed457..764573d 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -89,6 +89,12 @@
private static final boolean ENABLE_REALLY_DISABLE_PATTERN =
!REALLY_DISABLE_PATTERN.pattern().isEmpty();
+ /**
+ * If true, enable optional validation on running tests.
+ */
+ private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals(
+ System.getenv("RAVENWOOD_OPTIONAL_VALIDATION"));
+
static {
if (ENABLE_PROBE_IGNORED) {
System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
@@ -275,6 +281,12 @@
}
}
+ private void commonPrologue(Statement base, Description description) {
+ RavenwoodRuleImpl.logTestRunner("started", description);
+ RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION);
+ RavenwoodRuleImpl.init(RavenwoodRule.this);
+ }
+
/**
* Run the given {@link Statement} with no special treatment.
*/
@@ -284,8 +296,7 @@
public void evaluate() throws Throwable {
Assume.assumeTrue(shouldEnableOnRavenwood(description));
- RavenwoodRuleImpl.logTestRunner("started", description);
- RavenwoodRuleImpl.init(RavenwoodRule.this);
+ commonPrologue(base, description);
try {
base.evaluate();
RavenwoodRuleImpl.logTestRunner("finished", description);
@@ -310,8 +321,7 @@
public void evaluate() throws Throwable {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
- RavenwoodRuleImpl.logTestRunner("started", description);
- RavenwoodRuleImpl.init(RavenwoodRule.this);
+ commonPrologue(base, description);
try {
base.evaluate();
} catch (Throwable t) {
@@ -331,4 +341,11 @@
}
};
}
+
+ /**
+ * Do not use it outside ravenwood core classes.
+ */
+ public boolean _ravenwood_private$isOptionalValidationEnabled() {
+ return ENABLE_OPTIONAL_VALIDATION;
+ }
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 7d172f2..e951351b 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -17,6 +17,7 @@
package android.platform.test.ravenwood;
import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
public class RavenwoodRuleImpl {
public static boolean isOnRavenwood() {
@@ -34,4 +35,8 @@
public static void logTestRunner(String label, Description description) {
// No-op when running on a real device
}
+
+ public static void validate(Statement base, Description description,
+ boolean enableOptionalValidation) {
+ }
}
diff --git a/ravenwood/minimum-test/Android.bp b/ravenwood/minimum-test/Android.bp
index bf3583c..e4ed3d5 100644
--- a/ravenwood/minimum-test/Android.bp
+++ b/ravenwood/minimum-test/Android.bp
@@ -13,6 +13,7 @@
static_libs: [
"androidx.annotation_annotation",
+ "androidx.test.ext.junit",
"androidx.test.rules",
],
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
index 7abfecf..03cfad5 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
@@ -18,7 +18,7 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Rule;
diff --git a/services/contextualsearch/OWNERS b/services/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/services/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index a493d7a..797a2e6 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -24,6 +24,8 @@
import android.content.ComponentName;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SignedPackage;
import android.os.Build;
import android.os.CarrierAssociatedAppEntry;
import android.os.Environment;
@@ -350,6 +352,16 @@
// updated to avoid cached/potentially tampered results.
private final Set<String> mPreinstallPackagesWithStrictSignatureCheck = new ArraySet<>();
+ // A set of packages that should be considered "trusted packages" by ECM (Enhanced
+ // Confirmation Mode). "Trusted packages" are exempt from ECM (i.e., they will never be
+ // considered "restricted").
+ private final ArraySet<SignedPackage> mEnhancedConfirmationTrustedPackages = new ArraySet<>();
+
+ // A set of packages that should be considered "trusted installers" by ECM (Enhanced
+ // Confirmation Mode). "Trusted installers", and all apps installed by a trusted installer, are
+ // exempt from ECM (i.e., they will never be considered "restricted").
+ private final ArraySet<SignedPackage> mEnhancedConfirmationTrustedInstallers = new ArraySet<>();
+
/**
* Map of system pre-defined, uniquely named actors; keys are namespace,
* value maps actor name to package name.
@@ -560,6 +572,14 @@
return mPreinstallPackagesWithStrictSignatureCheck;
}
+ public ArraySet<SignedPackage> getEnhancedConfirmationTrustedPackages() {
+ return mEnhancedConfirmationTrustedPackages;
+ }
+
+ public ArraySet<SignedPackage> getEnhancedConfirmationTrustedInstallers() {
+ return mEnhancedConfirmationTrustedInstallers;
+ }
+
/**
* Only use for testing. Do NOT use in production code.
* @param readPermissions false to create an empty SystemConfig; true to read the permissions.
@@ -1558,6 +1578,26 @@
}
}
} break;
+ case "enhanced-confirmation-trusted-package": {
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+ SignedPackage signedPackage = parseEnhancedConfirmationTrustedPackage(
+ parser, permFile, name);
+ if (signedPackage != null) {
+ mEnhancedConfirmationTrustedPackages.add(signedPackage);
+ }
+ break;
+ }
+ } // fall through if flag is not enabled
+ case "enhanced-confirmation-trusted-installer": {
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+ SignedPackage signedPackage = parseEnhancedConfirmationTrustedPackage(
+ parser, permFile, name);
+ if (signedPackage != null) {
+ mEnhancedConfirmationTrustedInstallers.add(signedPackage);
+ }
+ break;
+ }
+ } // fall through if flag is not enabled
default: {
Slog.w(TAG, "Tag " + name + " is unknown in "
+ permFile + " at " + parser.getPositionDescription());
@@ -1619,6 +1659,33 @@
}
}
+ private @Nullable SignedPackage parseEnhancedConfirmationTrustedPackage(XmlPullParser parser,
+ File permFile, String elementName) {
+ String pkgName = parser.getAttributeValue(null, "package");
+ if (TextUtils.isEmpty(pkgName)) {
+ Slog.w(TAG, "<" + elementName + "> without package " + permFile + " at "
+ + parser.getPositionDescription());
+ return null;
+ }
+
+ String certificateDigestStr = parser.getAttributeValue(null, "sha256-cert-digest");
+ if (TextUtils.isEmpty(certificateDigestStr)) {
+ Slog.w(TAG, "<" + elementName + "> without sha256-cert-digest in " + permFile
+ + " at " + parser.getPositionDescription());
+ return null;
+ }
+ byte[] certificateDigest = null;
+ try {
+ certificateDigest = new Signature(certificateDigestStr).toByteArray();
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "<" + elementName + "> with invalid sha256-cert-digest in "
+ + permFile + " at " + parser.getPositionDescription());
+ return null;
+ }
+
+ return new SignedPackage(pkgName, certificateDigest);
+ }
+
// This method only enables a new Android feature added in U and will not have impact on app
// compatibility
@SuppressWarnings("AndroidFrameworkCompatChange")
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 77cb08b..cb15abc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -914,6 +914,9 @@
}
mOverrideRequestController.dumpInternal(pw);
+ pw.println();
+
+ mDeviceStatePolicy.dump(pw, /* args= */ null);
}
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
index 5c4e2f3..65e5085 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
+import android.util.Dumpable;
import com.android.server.policy.DeviceStatePolicyImpl;
@@ -29,7 +30,7 @@
*
* @see DeviceStateManagerService
*/
-public abstract class DeviceStatePolicy {
+public abstract class DeviceStatePolicy implements Dumpable {
protected final Context mContext;
protected DeviceStatePolicy(@NonNull Context context) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 50ab3f8..d5945f4 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
+import android.util.Dumpable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -31,7 +32,7 @@
*
* @see DeviceStatePolicy
*/
-public interface DeviceStateProvider {
+public interface DeviceStateProvider extends Dumpable {
int SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT = 0;
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a15cb10..a23c08a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -716,7 +716,10 @@
// Programmed
int programedInfo = params[offset] & 0x0F;
if (isValidProgrammedInfo(programedInfo)) {
- if (programedInfo == 0x09 || programedInfo == 0x0B) {
+ offset = offset + 1;
+ // Duration Available (2 bytes)
+ if ((programedInfo == 0x09 || programedInfo == 0x0B)
+ && params.length - offset >= 2) {
durationAvailable = true;
} else {
return true;
@@ -726,16 +729,17 @@
// Non programmed
int nonProgramedErrorInfo = params[offset] & 0x0F;
if (isValidNotProgrammedErrorInfo(nonProgramedErrorInfo)) {
- if (nonProgramedErrorInfo == 0x0E) {
+ offset = offset + 1;
+ // Duration Available (2 bytes)
+ if (nonProgramedErrorInfo == 0x0E && params.length - offset >= 2) {
durationAvailable = true;
} else {
return true;
}
}
}
- offset = offset + 1;
// Duration Available (2 bytes)
- if (durationAvailable && params.length - offset >= 2) {
+ if (durationAvailable) {
return (isValidDurationHours(params[offset]) && isValidMinute(params[offset + 1]));
}
return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index cb00e44..fff4939 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3185,24 +3185,6 @@
}
}
}
-
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- String ime = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
- String defaultDeviceIme = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- if (DEBUG) {
- Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
- + " device input method for user " + mSettings.getUserId()
- + " - restoring " + defaultDeviceIme);
- }
- SecureSettingsWrapper.putString(
- Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
- mSettings.getUserId());
- }
- }
-
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -5388,11 +5370,7 @@
if (!setSubtypeOnly) {
// Set InputMethod here
- final String imeId = imi != null ? imi.getId() : "";
- mSettings.putSelectedInputMethod(imeId);
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- mSettings.putSelectedDefaultDeviceInputMethod(imeId);
- }
+ mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
}
@@ -5535,9 +5513,6 @@
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- settings.putSelectedDefaultDeviceInputMethod(imeId);
- }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
return true;
}
@@ -6584,9 +6559,6 @@
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- settings.putSelectedDefaultDeviceInputMethod(nextIme);
- }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index e444db1..a51002b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,6 +36,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -87,6 +88,12 @@
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
+ String ime = getSelectedInputMethod();
+ String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ putSelectedInputMethod(defaultDeviceIme);
+ putSelectedDefaultDeviceInputMethod(null);
+ }
}
@AnyThread
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1c9bbab..638382e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7955,8 +7955,8 @@
&& mTelecomManager != null) {
try {
return mTelecomManager.isInManagedCall()
- || mTelecomManager.isInSelfManagedCall(
- pkg, UserHandle.getUserHandleForUid(uid));
+ || mTelecomManager.isInSelfManagedCall(pkg,
+ UserHandle.getUserHandleForUid(uid), /* hasCrossUserAccess */ true);
} catch (IllegalStateException ise) {
// Telecom is not ready (this is likely early boot), so there are no calls.
return false;
@@ -8844,19 +8844,26 @@
}
}
+ private PendingIntent getNotificationTimeoutPendingIntent(NotificationRecord record,
+ int flags) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ return PendingIntent.getBroadcast(getContext(),
+ REQUEST_CODE_TIMEOUT,
+ new Intent(ACTION_NOTIFICATION_TIMEOUT)
+ .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+ .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+ .appendPath(record.getKey()).build())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_KEY, record.getKey()),
+ flags);
+ }
+
@VisibleForTesting
@GuardedBy("mNotificationLock")
void scheduleTimeoutLocked(NotificationRecord record) {
if (record.getNotification().getTimeoutAfter() > 0) {
- final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
- REQUEST_CODE_TIMEOUT,
- new Intent(ACTION_NOTIFICATION_TIMEOUT)
- .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
- .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
- .appendPath(record.getKey()).build())
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_KEY, record.getKey()),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ final PendingIntent pi = getNotificationTimeoutPendingIntent(
+ record, PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi);
}
@@ -8864,6 +8871,16 @@
@VisibleForTesting
@GuardedBy("mNotificationLock")
+ void cancelScheduledTimeoutLocked(NotificationRecord record) {
+ final PendingIntent pi = getNotificationTimeoutPendingIntent(
+ record, PendingIntent.FLAG_CANCEL_CURRENT);
+ if (pi != null) {
+ mAlarmManager.cancel(pi);
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mNotificationLock")
/**
* Determine whether this notification should attempt to make noise, vibrate, or flash the LED
* @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
@@ -9894,21 +9911,7 @@
int rank, int count, boolean wasPosted, String listenerName,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
final String canceledKey = r.getKey();
-
- // Get pending intent used to create alarm, use FLAG_NO_CREATE if PendingIntent
- // does not already exist, then null will be returned.
- final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
- REQUEST_CODE_TIMEOUT,
- new Intent(ACTION_NOTIFICATION_TIMEOUT)
- .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
- .appendPath(r.getKey()).build())
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
-
- // Cancel alarm corresponding to pi.
- if (pi != null) {
- mAlarmManager.cancel(pi);
- }
+ cancelScheduledTimeoutLocked(r);
// Record caller.
recordCallerLocked(r);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 1786ac5..6ab4b99 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -308,31 +308,43 @@
return light;
}
+ private VibrationEffect getVibrationForChannel(
+ NotificationChannel channel, VibratorHelper helper, boolean insistent) {
+ if (!channel.shouldVibrate()) {
+ return null;
+ }
+
+ if (Flags.notificationChannelVibrationEffectApi()) {
+ final VibrationEffect vibration = channel.getVibrationEffect();
+ if (vibration != null && helper.areEffectComponentsSupported(vibration)) {
+ // Adjust the vibration's repeat behavior based on the `insistent` property.
+ return vibration.applyRepeatingIndefinitely(insistent, /* loopDelayMs= */ 0);
+ }
+ }
+
+ final long[] vibrationPattern = channel.getVibrationPattern();
+ if (vibrationPattern == null) {
+ return helper.createDefaultVibration(insistent);
+ }
+ return helper.createWaveformVibration(vibrationPattern, insistent);
+ }
+
private VibrationEffect calculateVibration() {
VibratorHelper helper = new VibratorHelper(mContext);
final Notification notification = getSbn().getNotification();
final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0;
- VibrationEffect defaultVibration = helper.createDefaultVibration(insistent);
- VibrationEffect vibration;
- if (getChannel().shouldVibrate()) {
- vibration = getChannel().getVibrationPattern() == null
- ? defaultVibration
- : helper.createWaveformVibration(getChannel().getVibrationPattern(), insistent);
- } else {
- vibration = null;
- }
+
if (mPreChannelsNotification
&& (getChannel().getUserLockedFields()
& NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
if (useDefaultVibrate) {
- vibration = defaultVibration;
- } else {
- vibration = helper.createWaveformVibration(notification.vibrate, insistent);
+ return helper.createDefaultVibration(insistent);
}
+ return helper.createWaveformVibration(notification.vibrate, insistent);
}
- return vibration;
+ return getVibrationForChannel(getChannel(), helper, insistent);
}
private @NonNull AudioAttributes calculateAttributes() {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bafcfe..4f3cdbc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1212,6 +1212,8 @@
if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0
&& (!Arrays.equals(oldParent.getVibrationPattern(),
updatedParent.getVibrationPattern())
+ || !Objects.equals(
+ oldParent.getVibrationEffect(), updatedParent.getVibrationEffect())
|| oldParent.shouldVibrate() != updatedParent.shouldVibrate())) {
// enableVibration must be 2nd because setVibrationPattern may toggle it.
conversation.setVibrationPattern(updatedParent.getVibrationPattern());
@@ -1972,6 +1974,7 @@
update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
}
if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
+ || !Objects.equals(original.getVibrationEffect(), update.getVibrationEffect())
|| original.shouldVibrate() != update.shouldVibrate()) {
update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
}
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index 7204d05..8a0e595 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -193,6 +193,11 @@
return createWaveformVibration(mDefaultPattern, insistent);
}
+ /** Returns if a given vibration can be played by the vibrator that does notification buzz. */
+ public boolean areEffectComponentsSupported(VibrationEffect effect) {
+ return mVibrator.areVibrationFeaturesSupported(effect);
+ }
+
@Nullable
private static float[] getFloatArray(Resources resources, int resId) {
TypedArray array = resources.obtainTypedArray(resId);
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 339b1e7..0bbad85 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -28,6 +28,7 @@
import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
+import static android.content.pm.PackageManager.DELETE_ALL_USERS;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
@@ -182,62 +183,76 @@
return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
}
+ @VisibleForTesting
void requestArchive(
@NonNull String packageName,
@NonNull String callerPackageName,
@NonNull IntentSender intentSender,
@NonNull UserHandle userHandle) {
+ requestArchive(packageName, callerPackageName, /*flags=*/ 0, intentSender, userHandle);
+ }
+
+ void requestArchive(
+ @NonNull String packageName,
+ @NonNull String callerPackageName,
+ int flags,
+ @NonNull IntentSender intentSender,
+ @NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
Objects.requireNonNull(intentSender);
Objects.requireNonNull(userHandle);
Computer snapshot = mPm.snapshotComputer();
- int userId = userHandle.getIdentifier();
+ int binderUserId = userHandle.getIdentifier();
int binderUid = Binder.getCallingUid();
int binderPid = Binder.getCallingPid();
if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) {
- verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
+ verifyCaller(snapshot.getPackageUid(callerPackageName, 0, binderUserId), binderUid);
}
- snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
- "archiveApp");
+
+ final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0;
+ final int[] users = deleteAllUsers ? mPm.mInjector.getUserManagerInternal().getUserIds()
+ : new int[]{binderUserId};
+ for (int userId : users) {
+ snapshot.enforceCrossUserPermission(binderUid, userId,
+ /*requireFullPermission=*/ true, /*checkShell=*/ true,
+ "archiveApp");
+ }
verifyUninstallPermissions();
- CompletableFuture<ArchiveState> archiveStateFuture;
+ CompletableFuture<Void>[] archiveStateStored = new CompletableFuture[users.length];
try {
- archiveStateFuture = createArchiveState(packageName, userId);
+ for (int i = 0, size = users.length; i < size; ++i) {
+ archiveStateStored[i] = createAndStoreArchiveState(packageName, users[i]);
+ }
} catch (PackageManager.NameNotFoundException e) {
Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
packageName, e.getMessage()));
throw new ParcelableException(e);
}
- archiveStateFuture
- .thenAccept(
- archiveState -> {
- // TODO(b/282952870) Should be reverted if uninstall fails/cancels
- try {
- storeArchiveState(packageName, archiveState, userId);
- } catch (PackageManager.NameNotFoundException e) {
- sendFailureStatus(intentSender, packageName, e.getMessage());
- return;
- }
+ final int deleteFlags = DELETE_ARCHIVE | DELETE_KEEP_DATA
+ | (deleteAllUsers ? DELETE_ALL_USERS : 0);
- mPm.mInstallerService.uninstall(
- new VersionedPackage(packageName,
- PackageManager.VERSION_CODE_HIGHEST),
- callerPackageName,
- DELETE_ARCHIVE | DELETE_KEEP_DATA,
- intentSender,
- userId,
- binderUid,
- binderPid);
- })
- .exceptionally(
- e -> {
- sendFailureStatus(intentSender, packageName, e.getMessage());
- return null;
- });
+ CompletableFuture.allOf(archiveStateStored).thenAccept(ignored ->
+ mPm.mInstallerService.uninstall(
+ new VersionedPackage(packageName,
+ PackageManager.VERSION_CODE_HIGHEST),
+ callerPackageName,
+ deleteFlags,
+ intentSender,
+ binderUserId,
+ binderUid,
+ binderPid)
+ ).exceptionally(
+ e -> {
+ Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
+ packageName, e.getMessage()));
+ sendFailureStatus(intentSender, packageName, e.getMessage());
+ return null;
+ }
+ );
}
/**
@@ -384,7 +399,7 @@
}
/** Creates archived state for the package and user. */
- private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
+ private CompletableFuture<Void> createAndStoreArchiveState(String packageName, int userId)
throws PackageManager.NameNotFoundException {
Computer snapshot = mPm.snapshotComputer();
PackageStateInternal ps = getPackageState(packageName, snapshot,
@@ -399,17 +414,18 @@
List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
userId);
- final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>();
+ final CompletableFuture<Void> archiveStateStored = new CompletableFuture<>();
mPm.mHandler.post(() -> {
try {
- archiveState.complete(
- createArchiveStateInternal(packageName, userId, mainActivities,
- installerInfo.loadLabel(mContext.getPackageManager()).toString()));
- } catch (IOException e) {
- archiveState.completeExceptionally(e);
+ var archiveState = createArchiveStateInternal(packageName, userId, mainActivities,
+ installerInfo.loadLabel(mContext.getPackageManager()).toString());
+ storeArchiveState(packageName, archiveState, userId);
+ archiveStateStored.complete(null);
+ } catch (IOException | PackageManager.NameNotFoundException e) {
+ archiveStateStored.completeExceptionally(e);
}
});
- return archiveState;
+ return archiveStateStored;
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c6d448d..abea56b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1672,9 +1672,11 @@
public void requestArchive(
@NonNull String packageName,
@NonNull String callerPackageName,
+ int flags,
@NonNull IntentSender intentSender,
@NonNull UserHandle userHandle) {
- mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, userHandle);
+ mPackageArchiver.requestArchive(packageName, callerPackageName, flags, intentSender,
+ userHandle);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5c9c8c6..81f9d1b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4651,6 +4651,7 @@
private int runArchive() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
+ int flags = 0;
int userId = UserHandle.USER_ALL;
String opt;
@@ -4678,13 +4679,16 @@
return 1;
}
+ if (userId == UserHandle.USER_ALL) {
+ flags |= PackageManager.DELETE_ALL_USERS;
+ }
final int translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
final LocalIntentReceiver receiver = new LocalIntentReceiver();
try {
mInterface.getPackageInstaller().requestArchive(packageName,
- /* callerPackageName= */ "", receiver.getIntentSender(),
+ /* callerPackageName= */ "", flags, receiver.getIntentSender(),
new UserHandle(translatedUserId));
} catch (Exception e) {
pw.println("Failure [" + e.getMessage() + "]");
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
index 7754944..07cc775 100644
--- a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -17,11 +17,14 @@
package com.android.server.policy;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import com.android.server.devicestate.DeviceStatePolicy;
import com.android.server.devicestate.DeviceStateProvider;
+import java.io.PrintWriter;
+
/**
* Default empty implementation of {@link DeviceStatePolicy}.
*
@@ -43,4 +46,9 @@
public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
onComplete.run();
}
+
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ mProvider.dump(writer, args);
+ }
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 3644054..afcf5a0 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -58,6 +58,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
@@ -503,6 +504,24 @@
// Do nothing.
}
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ writer.println("DeviceStateProviderImpl");
+
+ synchronized (mLock) {
+ writer.println(" mLastReportedState = " + mLastReportedState);
+ writer.println(" mPowerSaveModeEnabled = " + mPowerSaveModeEnabled);
+ writer.println(" mThermalStatus = " + mThermalStatus);
+ writer.println(" mIsLidOpen = " + mIsLidOpen);
+ writer.println(" Sensor values:");
+
+ for (Sensor sensor : mLatestSensorEvent.keySet()) {
+ SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
+ writer.println(" - " + toSensorValueString(sensor, sensorEvent));
+ }
+ }
+ }
+
/**
* Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid
* switch open state matches {@link #mIsLidOpen}.
@@ -669,14 +688,16 @@
Slog.i(TAG, "Sensor values:");
for (Sensor sensor : mLatestSensorEvent.keySet()) {
SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
- if (sensorEvent != null) {
- Slog.i(TAG, sensor.getName() + ": " + Arrays.toString(sensorEvent.values));
- } else {
- Slog.i(TAG, sensor.getName() + ": null");
- }
+ Slog.i(TAG, toSensorValueString(sensor, sensorEvent));
}
}
+ private String toSensorValueString(Sensor sensor, @Nullable SensorEvent event) {
+ String sensorString = sensor == null ? "null" : sensor.getName();
+ String eventValues = event == null ? "null" : Arrays.toString(event.values);
+ return sensorString + " : " + eventValues;
+ }
+
/**
* Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
* {@code null} if the file could not be successfully parsed.
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
index 906da2f..b05a421 100644
--- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -59,6 +59,10 @@
final Set<ComponentName> enabledServices =
AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
ComponentName componentName = getTalkbackComponent();
+ if (componentName == null) {
+ return false;
+ }
+
boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
if (isTalkBackShortcutGestureEnabled()) {
@@ -67,7 +71,7 @@
isTalkbackAlreadyEnabled);
// log stem triple press telemetry if it's a talkback enabled event.
- if (componentName != null && isTalkbackAlreadyEnabled) {
+ if (isTalkbackAlreadyEnabled) {
logStemTriplePressAccessibilityTelemetry(componentName);
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 089a886..e0a8226 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2095,17 +2095,17 @@
}
@Override
- public void startPlayback(IBinder sessionToken, int userId) {
+ public void resumePlayback(IBinder sessionToken, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
- userId, "stopPlayback");
+ userId, "resumePlayback");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
- getSessionLocked(sessionToken, callingUid, resolvedUserId).startPlayback();
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).resumePlayback();
} catch (RemoteException | SessionNotFoundException e) {
- Slog.e(TAG, "error in startPlayback()", e);
+ Slog.e(TAG, "error in resumePlayback()", e);
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 27cc2d6..7fc61e1 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -66,7 +66,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.WeakHashMap;
-import java.util.function.Consumer;
/**
* Stores the TaskOrganizers associated with a given windowing mode and
@@ -102,11 +101,8 @@
*/
private static class TaskOrganizerCallbacks {
final ITaskOrganizer mTaskOrganizer;
- final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
- TaskOrganizerCallbacks(ITaskOrganizer taskOrg,
- Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
- mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
+ TaskOrganizerCallbacks(ITaskOrganizer taskOrg) {
mTaskOrganizer = taskOrg;
}
@@ -335,11 +331,7 @@
private final int mUid;
TaskOrganizerState(ITaskOrganizer organizer, int uid) {
- final Consumer<Runnable> deferTaskOrgCallbacksConsumer =
- mDeferTaskOrgCallbacksConsumer != null
- ? mDeferTaskOrgCallbacksConsumer
- : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
- mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
+ mOrganizer = new TaskOrganizerCallbacks(organizer);
mDeathRecipient = new DeathRecipient(organizer);
mPendingEventsQueue = new TaskOrganizerPendingEventsQueue(this);
try {
@@ -484,8 +476,6 @@
// Set of organized tasks (by taskId) that dispatch back pressed to their organizers
private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet<>();
- private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
-
TaskOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
@@ -502,15 +492,6 @@
}
/**
- * Specifies the consumer to run to defer the task org callbacks. Can be overridden while
- * testing to allow the callbacks to be sent synchronously.
- */
- @VisibleForTesting
- public void setDeferTaskOrgCallbacksConsumer(Consumer<Runnable> consumer) {
- mDeferTaskOrgCallbacksConsumer = consumer;
- }
-
- /**
* Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
*/
@Override
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 1688a1a..817901f 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -32,7 +32,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.Size;
@@ -159,10 +158,6 @@
private InputWindowHandle[] mLastWindowHandles;
- private final Object mIgnoredWindowTokensLock = new Object();
-
- private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
-
private void startHandlerThreadIfNeeded() {
synchronized (mHandlerThreadLock) {
if (mHandler == null) {
@@ -173,18 +168,6 @@
}
}
- void addIgnoredWindowTokens(IBinder token) {
- synchronized (mIgnoredWindowTokensLock) {
- mIgnoredWindowTokens.add(token);
- }
- }
-
- void removeIgnoredWindowTokens(IBinder token) {
- synchronized (mIgnoredWindowTokensLock) {
- mIgnoredWindowTokens.remove(token);
- }
- }
-
void registerListener(IBinder window, ITrustedPresentationListener listener,
TrustedPresentationThresholds thresholds, int id) {
startHandlerThreadIfNeeded();
@@ -271,12 +254,8 @@
ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
new ArrayMap<>();
- ArraySet<IBinder> ignoredWindowTokens;
- synchronized (mIgnoredWindowTokensLock) {
- ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
- }
for (var windowHandle : mLastWindowHandles) {
- if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
+ if (!windowHandle.canOccludePresentation) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 24e50c5..f5806c0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1148,10 +1148,6 @@
parentWindow.addChild(this, sWindowSubLayerComparator);
}
- if (token.mRoundedCornerOverlay) {
- mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
- getWindowToken());
- }
}
@Override
@@ -1163,6 +1159,9 @@
if (secureWindowState()) {
getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
}
+ // All apps should be considered as occluding when computing TrustedPresentation Thresholds.
+ final boolean canOccludePresentation = !mSession.mCanAddInternalSystemWindow;
+ getPendingTransaction().setCanOccludePresentation(mSurfaceControl, canOccludePresentation);
}
void updateTrustedOverlay() {
@@ -2344,9 +2343,6 @@
mSession.onWindowRemoved(this);
mWmService.postWindowRemoveCleanupLocked(this);
- mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
- getWindowToken());
-
consumeInsetsChange();
}
diff --git a/services/core/jni/com_android_server_am_OomConnection.cpp b/services/core/jni/com_android_server_am_OomConnection.cpp
index e892d23..49a3ad3 100644
--- a/services/core/jni/com_android_server_am_OomConnection.cpp
+++ b/services/core/jni/com_android_server_am_OomConnection.cpp
@@ -22,13 +22,15 @@
namespace android {
+using namespace ::android::bpf::memevents;
+
// Used to cache the results of the JNI name lookup
static struct {
jclass clazz;
jmethodID ctor;
} sOomKillRecordInfo;
-static memevents::MemEventListener memevent_listener;
+static MemEventListener memevent_listener(MemEventClient::AMS);
/**
* Initialize listening and waiting for new out-of-memory (OOM) events to occur.
@@ -42,25 +44,20 @@
* @throws java.lang.RuntimeException
*/
static jobjectArray android_server_am_OomConnection_waitOom(JNIEnv* env, jobject) {
- const memevents::MemEvent oom_event = memevents::MemEvent::OOM_KILL;
- if (!memevent_listener.registerEvent(oom_event)) {
+ if (!memevent_listener.registerEvent(MEM_EVENT_OOM_KILL)) {
memevent_listener.deregisterAllEvents();
jniThrowRuntimeException(env, "listener failed to register to OOM events");
return nullptr;
}
- memevents::MemEvent event_received;
- do {
- event_received = memevent_listener.listen();
- if (event_received == memevents::MemEvent::ERROR) {
- memevent_listener.deregisterAllEvents();
- jniThrowRuntimeException(env, "listener received error event");
- return nullptr;
- }
- } while (event_received != oom_event);
+ if (!memevent_listener.listen()) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "listener failed waiting for OOM event");
+ return nullptr;
+ }
- std::vector<memevents::OomKill> oom_events;
- if (!memevent_listener.getOomEvents(oom_events)) {
+ std::vector<mem_event_t> oom_events;
+ if (!memevent_listener.getMemEvents(oom_events)) {
memevent_listener.deregisterAllEvents();
jniThrowRuntimeException(env, "Failed to get OOM events");
return nullptr;
@@ -75,15 +72,23 @@
}
for (int i = 0; i < oom_events.size(); i++) {
- const memevents::OomKill oom_event = oom_events[i];
- jstring process_name = env->NewStringUTF(oom_event.process_name);
+ const mem_event_t mem_event = oom_events[i];
+ if (mem_event.type != MEM_EVENT_OOM_KILL) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "Received invalid memory event");
+ return java_oom_array;
+ }
+
+ const auto oom_kill = mem_event.event_data.oom_kill;
+
+ jstring process_name = env->NewStringUTF(oom_kill.process_name);
if (process_name == NULL) {
memevent_listener.deregisterAllEvents();
jniThrowRuntimeException(env, "Failed creating java string for process name");
}
jobject java_oom_kill = env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor,
- oom_event.timestamp_ms, oom_event.pid, oom_event.uid,
- process_name, oom_event.oom_score_adj);
+ oom_kill.timestamp_ms, oom_kill.pid, oom_kill.uid,
+ process_name, oom_kill.oom_score_adj);
if (java_oom_kill == NULL) {
memevent_listener.deregisterAllEvents();
jniThrowRuntimeException(env, "Failed to create OomKillRecord object");
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 4203576..e8b32cd 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -178,7 +178,7 @@
// intents
return PendingIntent.getActivityAsUser(
mContext, /*requestCode=*/0, intent,
- PendingIntent.FLAG_IMMUTABLE, /*options=*/null,
+ PendingIntent.FLAG_MUTABLE, /*options=*/null,
UserHandle.of(mUserId));
}
}
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 7e709fe..1a9a0e6 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -116,7 +116,7 @@
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
- providerDataList,
+ /*providerDataList=*/ null,
/*isRequestForAllOptions=*/ true);
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index fcaef9f..ca23d62 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -30,6 +30,7 @@
import android.credentials.selection.Entry;
import android.credentials.selection.GetCredentialProviderData;
import android.credentials.selection.ProviderPendingIntentResponse;
+import android.os.Bundle;
import android.os.ICancellationSignal;
import android.service.autofill.Flags;
import android.service.credentials.Action;
@@ -76,6 +77,9 @@
@NonNull
private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
+ @NonNull
+ private final Map<String, AutofillId> mCredentialEntryKeyToAutofilLIdMap;
+
/** The complete request to be used in the second round. */
private final android.credentials.GetCredentialRequest mCompleteRequest;
@@ -245,6 +249,7 @@
mBeginGetOptionToCredentialOptionMap = new HashMap<>(beginGetOptionToCredentialOptionMap);
mProviderResponseDataHandler = new ProviderResponseDataHandler(
ComponentName.unflattenFromString(hybridService));
+ mCredentialEntryKeyToAutofilLIdMap = new HashMap<>();
}
/** Called when the provider response has been updated by an external source. */
@@ -298,7 +303,7 @@
invokeCallbackOnInternalInvalidState();
return;
}
- onCredentialEntrySelected(providerPendingIntentResponse);
+ onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
break;
case ACTION_ENTRY_KEY:
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
@@ -307,7 +312,7 @@
invokeCallbackOnInternalInvalidState();
return;
}
- onActionEntrySelected(providerPendingIntentResponse);
+ onActionEntrySelected(providerPendingIntentResponse, entryKey);
break;
case AUTHENTICATION_ACTION_ENTRY_KEY:
Action authenticationEntry = mProviderResponseDataHandler
@@ -337,7 +342,7 @@
break;
case REMOTE_ENTRY_KEY:
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
- onRemoteEntrySelected(providerPendingIntentResponse);
+ onRemoteEntrySelected(providerPendingIntentResponse, entryKey);
} else {
Slog.i(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
@@ -376,7 +381,7 @@
return null;
}
- private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
+ private Intent setUpFillInIntentWithFinalRequest(@NonNull String id, String entryKey) {
// TODO: Determine if we should skip this entry if entry id is not set, or is set
// but does not resolve to a valid option. For now, not skipping it because
// it may be possible that the provider adds their own extras and expects to receive
@@ -392,6 +397,7 @@
.getParcelable(CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class);
if (autofillId != null && Flags.autofillCredmanIntegration()) {
intent.putExtra(CredentialProviderService.EXTRA_AUTOFILL_ID, autofillId);
+ mCredentialEntryKeyToAutofilLIdMap.put(entryKey, autofillId);
}
return intent.putExtra(
CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -408,12 +414,13 @@
}
private void onRemoteEntrySelected(
- ProviderPendingIntentResponse providerPendingIntentResponse) {
- onCredentialEntrySelected(providerPendingIntentResponse);
+ ProviderPendingIntentResponse providerPendingIntentResponse, String entryKey) {
+ onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
}
private void onCredentialEntrySelected(
- ProviderPendingIntentResponse providerPendingIntentResponse) {
+ ProviderPendingIntentResponse providerPendingIntentResponse,
+ String entryKey) {
if (providerPendingIntentResponse == null) {
invokeCallbackOnInternalInvalidState();
return;
@@ -430,7 +437,18 @@
GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
.extractGetCredentialResponse(
providerPendingIntentResponse.getResultData());
- if (getCredentialResponse != null) {
+ if (getCredentialResponse != null && getCredentialResponse.getCredential() != null) {
+ Bundle credentialData = getCredentialResponse.getCredential().getData();
+ AutofillId autofillId = mCredentialEntryKeyToAutofilLIdMap.get(entryKey);
+ if (Flags.autofillCredmanIntegration()
+ && entryKey != null && autofillId != null && credentialData != null
+ ) {
+ Slog.d(TAG, "Adding autofillId to credential response: " + autofillId);
+ credentialData.putParcelable(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ mCredentialEntryKeyToAutofilLIdMap.get(entryKey)
+ );
+ }
mCallbacks.onFinalResponseReceived(mComponentName,
getCredentialResponse);
return;
@@ -514,9 +532,9 @@
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
- providerPendingIntentResponse) {
+ providerPendingIntentResponse, String entryKey) {
Slog.i(TAG, "onActionEntrySelected");
- onCredentialEntrySelected(providerPendingIntentResponse);
+ onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
}
@@ -614,7 +632,7 @@
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
setUpFillInIntentWithFinalRequest(credentialEntry
- .getBeginGetCredentialOptionId()));
+ .getBeginGetCredentialOptionId(), id));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
mCredentialEntryTypes.add(credentialEntry.getType());
}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index d5a3cff..82d5247 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -35,6 +35,7 @@
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.util.ArraySet;
+import android.util.Dumpable;
import android.view.Display;
import android.view.Surface;
@@ -43,7 +44,9 @@
import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
+import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
@@ -56,7 +59,7 @@
* See {@link BookStyleStateTransitions} for detailed description of the default behavior.
*/
public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
- DisplayManager.DisplayListener {
+ DisplayManager.DisplayListener, Dumpable {
private final BookStylePreferredScreenCalculator mClosedStateCalculator;
private final Handler mHandler = new Handler();
@@ -154,6 +157,14 @@
}
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ writer.println(" " + getDumpableName());
+
+ mPostureEstimator.dump(writer, args);
+ mClosedStateCalculator.dump(writer, args);
+ }
+
public interface ClosedStateUpdatesListener {
void onClosedStateUpdated();
}
@@ -161,7 +172,7 @@
/**
* Estimates if the device is going to enter wedge/tent mode based on the sensor data
*/
- private static class PostureEstimator implements SensorEventListener {
+ private static class PostureEstimator implements SensorEventListener, Dumpable {
private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8;
@@ -356,6 +367,23 @@
mDeviceClosed = deviceClosed;
mConditionedSensorListener.updateListeningState();
}
+
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ writer.println(" " + getDumpableName());
+ writer.println(" isLikelyTentOrWedgeMode = " + isLikelyTentOrWedgeMode());
+ writer.println(" mScreenTurnedOn = " + mScreenTurnedOn);
+ writer.println(" mLastScreenRotation = " + mLastScreenRotation);
+ writer.println(" mDeviceClosed = " + mDeviceClosed);
+ writer.println(" mLeftGravityVector = " + Arrays.toString(mLeftGravityVector));
+ writer.println(" mRightGravityVector = " + Arrays.toString(mRightGravityVector));
+ }
+
+ @NonNull
+ @Override
+ public String getDumpableName() {
+ return "PostureEstimator";
+ }
}
/**
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index ad938af..8b22718 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -38,6 +38,7 @@
import com.android.server.policy.feature.flags.FeatureFlags;
import com.android.server.policy.feature.flags.FeatureFlagsImpl;
+import java.io.PrintWriter;
import java.util.function.Predicate;
/**
@@ -182,4 +183,9 @@
public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
onComplete.run();
}
+
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ mProvider.dump(writer, args);
+ }
}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
index 8977422..69d793e 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
@@ -16,8 +16,14 @@
package com.android.server.policy;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Build;
+import android.util.Dumpable;
+import android.util.Slog;
+
+import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
@@ -31,7 +37,12 @@
*
* See {@link BookStyleStateTransitions} for detailed description of the default behavior.
*/
-public class BookStylePreferredScreenCalculator {
+public class BookStylePreferredScreenCalculator implements Dumpable {
+
+ private static final String TAG = "BookStylePreferredScreenCalculator";
+
+ // TODO(b/322137477): disable by default on all builds after flag clean-up
+ private static final boolean DEBUG = Build.IS_USERDEBUG || Build.IS_ENG;
/**
* When calculating the new state we will re-calculate it until it settles down. We re-calculate
@@ -77,6 +88,9 @@
*/
public PreferredScreen calculatePreferredScreen(HingeAngle angle, boolean likelyTentOrWedge,
boolean likelyReverseWedge) {
+
+ final State oldState = mState;
+
int attempts = 0;
State newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge);
while (attempts < MAX_STATE_CHANGES && !Objects.equals(mState, newState)) {
@@ -92,7 +106,6 @@
+ ", likelyReverseWedge = " + likelyReverseWedge);
}
- final State oldState = mState;
mState = newState;
if (mState.mPreferredScreen == PreferredScreen.INVALID) {
@@ -103,6 +116,13 @@
+ oldState);
}
+ if (DEBUG && !Objects.equals(oldState, newState)) {
+ Slog.d(TAG, "Moving to state " + mState
+ + " (hingeAngle = " + angle
+ + ", likelyTentOrWedge = " + likelyTentOrWedge
+ + ", likelyReverseWedge = " + likelyReverseWedge + ")");
+ }
+
return mState.mPreferredScreen;
}
@@ -129,6 +149,18 @@
+ likelyReverseWedge);
}
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ writer.println(" " + getDumpableName());
+ writer.println(" mState = " + mState);
+ }
+
+ @NonNull
+ @Override
+ public String getDumpableName() {
+ return TAG;
+ }
+
/**
* The angle between two halves of the foldable device in degrees. The angle is '0' when
* the device is fully closed and '180' when the device is fully open and flat.
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index ba72977..021a667 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -39,6 +39,7 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.Trace;
+import android.util.Dumpable;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -52,6 +53,7 @@
import com.android.server.policy.feature.flags.FeatureFlags;
import com.android.server.policy.feature.flags.FeatureFlagsImpl;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -87,6 +89,8 @@
// the conditions needed for availability.
private final SparseArray<BooleanSupplier> mStateAvailabilityConditions = new SparseArray<>();
+ private final DeviceStateConfiguration[] mConfigurations;
+
@GuardedBy("mLock")
private final SparseBooleanArray mExternalDisplaysConnected = new SparseBooleanArray();
@@ -142,6 +146,7 @@
mHingeAngleSensor = hingeAngleSensor;
mHallSensor = hallSensor;
mDisplayManager = displayManager;
+ mConfigurations = deviceStateConfigurations;
mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST);
@@ -350,16 +355,20 @@
@GuardedBy("mLock")
private void dumpSensorValues() {
Slog.i(TAG, "Sensor values:");
- dumpSensorValues("Hall Sensor", mHallSensor, mLastHallSensorEvent);
- dumpSensorValues("Hinge Angle Sensor", mHingeAngleSensor, mLastHingeAngleSensorEvent);
+ dumpSensorValues(mHallSensor, mLastHallSensorEvent);
+ dumpSensorValues(mHingeAngleSensor, mLastHingeAngleSensorEvent);
Slog.i(TAG, "isScreenOn: " + isScreenOn());
}
@GuardedBy("mLock")
- private void dumpSensorValues(String sensorType, Sensor sensor, @Nullable SensorEvent event) {
+ private void dumpSensorValues(Sensor sensor, @Nullable SensorEvent event) {
+ Slog.i(TAG, toSensorValueString(sensor, event));
+ }
+
+ private String toSensorValueString(Sensor sensor, @Nullable SensorEvent event) {
String sensorString = sensor == null ? "null" : sensor.getName();
String eventValues = event == null ? "null" : Arrays.toString(event.values);
- Slog.i(TAG, sensorType + " : " + sensorString + " : " + eventValues);
+ return sensorString + " : " + eventValues;
}
@Override
@@ -414,6 +423,34 @@
}
}
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ writer.println("FoldableDeviceStateProvider");
+
+ synchronized (mLock) {
+ writer.println(" mLastReportedState = " + mLastReportedState);
+ writer.println(" mPowerSaveModeEnabled = " + mPowerSaveModeEnabled);
+ writer.println(" mThermalStatus = " + mThermalStatus);
+ writer.println(" mLastHingeAngleSensorEvent = " +
+ toSensorValueString(mHingeAngleSensor, mLastHingeAngleSensorEvent));
+ writer.println(" mLastHallSensorEvent = " +
+ toSensorValueString(mHallSensor, mLastHallSensorEvent));
+ }
+
+ writer.println();
+ writer.println(" Predicates:");
+
+ for (int i = 0; i < mConfigurations.length; i++) {
+ final DeviceStateConfiguration configuration = mConfigurations[i];
+ final Predicate<FoldableDeviceStateProvider> predicate =
+ configuration.mActiveStatePredicate;
+
+ if (predicate instanceof Dumpable dumpable) {
+ dumpable.dump(writer, /* args= */ null);
+ }
+ }
+ }
+
/**
* Configuration for a single device state, contains information about the state like
* identifier, name, flags and a predicate that should return true if the state should
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index fd21a32..2359422c9 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -22,6 +22,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.SignedPackage;
+import android.content.pm.SignedPackageParcel;
import android.os.Binder;
import android.os.ISystemConfig;
import android.util.ArrayMap;
@@ -119,6 +121,26 @@
pmi.canQueryPackage(Binder.getCallingUid(), preventUserDisablePackage))
.collect(toList());
}
+
+ @Override
+ public List<SignedPackageParcel> getEnhancedConfirmationTrustedPackages() {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES,
+ "Caller must hold " + Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES);
+
+ return SystemConfig.getInstance().getEnhancedConfirmationTrustedPackages().stream()
+ .map(SignedPackage::getData).toList();
+ }
+
+ @Override
+ public List<SignedPackageParcel> getEnhancedConfirmationTrustedInstallers() {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES,
+ "Caller must hold " + Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES);
+
+ return SystemConfig.getInstance().getEnhancedConfirmationTrustedInstallers().stream()
+ .map(SignedPackage::getData).toList();
+ }
};
public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2b8bcc7..b79d20a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -269,6 +269,8 @@
"com.android.server.backup.BackupManagerService$Lifecycle";
private static final String APPWIDGET_SERVICE_CLASS =
"com.android.server.appwidget.AppWidgetService";
+ private static final String ARC_NETWORK_SERVICE_CLASS =
+ "com.android.server.arc.net.ArcNetworkService";
private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
"com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
private static final String ARC_SYSTEM_HEALTH_SERVICE =
@@ -2069,13 +2071,24 @@
if (context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI)) {
// Wifi Service must be started first for wifi-related services.
- t.traceBegin("StartWifi");
- mSystemServiceManager.startServiceFromJar(
- WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
- t.traceEnd();
- t.traceBegin("StartWifiScanning");
- mSystemServiceManager.startServiceFromJar(
- WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ if (!isArc) {
+ t.traceBegin("StartWifi");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ t.traceBegin("StartWifiScanning");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ }
+ }
+
+ // ARC - ArcNetworkService registers the ARC network stack and replaces the
+ // stock WiFi service in both ARC++ container and ARCVM. Always starts the ARC network
+ // stack regardless of whether FEATURE_WIFI is enabled/disabled (b/254755875).
+ if (isArc) {
+ t.traceBegin("StartArcNetworking");
+ mSystemServiceManager.startService(ARC_NETWORK_SERVICE_CLASS);
t.traceEnd();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index bf00b75..4535ece 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -270,7 +270,7 @@
assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
PackageInstaller.STATUS_FAILURE);
- assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).isEqualTo(
+ assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).contains(
String.format("Package %s not found.", PACKAGE));
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 7dcfc88..fa39364 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -30,6 +30,7 @@
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
+import android.annotation.NonNull;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateRequest;
import android.hardware.devicestate.IDeviceStateManagerCallback;
@@ -52,6 +53,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Optional;
@@ -959,6 +961,10 @@
}
onComplete.run();
}
+
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ }
}
private static final class TestDeviceStateProvider implements DeviceStateProvider {
@@ -1001,6 +1007,10 @@
public void setState(int identifier) {
mListener.onStateChanged(identifier);
}
+
+ @Override
+ public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+ }
}
private static final class TestDeviceStateManagerCallback extends
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index aca96ad..03cdbbd 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -21,8 +21,13 @@
import static org.junit.Assert.assertEquals;
import static org.testng.Assert.expectThrows;
+import android.content.pm.Signature;
+import android.content.pm.SignedPackage;
import android.os.Build;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -68,6 +73,8 @@
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ @Rule public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mSysConfig = new SystemConfigTestClass();
@@ -696,6 +703,43 @@
assertThat(mSysConfig.getSystemAppUpdateOwnerPackageName("com.foo")).isNull();
}
+ /**
+ * Tests that SystemConfig::getEnhancedConfirmationTrustedInstallers correctly parses a list of
+ * SignedPackage objects.
+ */
+ @Test
+ @RequiresFlagsEnabled(
+ android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ public void getEnhancedConfirmationTrustedInstallers_returnsTrustedInstallers()
+ throws IOException {
+ String pkgName = "com.example.app";
+ String certificateDigestStr = "E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:"
+ + "8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0";
+
+ byte[] certificateDigest = new Signature(certificateDigestStr).toByteArray();
+ String contents = "<config>"
+ + "<" + "enhanced-confirmation-trusted-installer" + " "
+ + "package=\"" + pkgName + "\""
+ + " sha256-cert-digest=\"" + certificateDigestStr + "\""
+ + "/>"
+ + "</config>";
+
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "enhanced-confirmation.xml", contents);
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ ArraySet<SignedPackage> actualTrustedInstallers =
+ mSysConfig.getEnhancedConfirmationTrustedInstallers();
+
+ assertThat(actualTrustedInstallers.size()).isEqualTo(1);
+ SignedPackage actual = actualTrustedInstallers.stream().findFirst().orElseThrow();
+ SignedPackage expected = new SignedPackage(pkgName, certificateDigest);
+
+ assertThat(actual.getCertificateDigest()).isEqualTo(expected.getCertificateDigest());
+ assertThat(actual.getPkgName()).isEqualTo(expected.getPkgName());
+ assertThat(actual).isEqualTo(expected);
+ }
+
private void parseSharedLibraries(String contents) throws IOException {
File folder = createTempSubfolder("permissions_folder");
createTempFile(folder, "permissions.xml", contents);
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index b8dcecd..e27bb4c 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -320,4 +320,10 @@
mWindowInfosReportedListeners.add(listener);
return this;
}
+
+ @Override
+ public SurfaceControl.Transaction setCanOccludePresentation(SurfaceControl sc,
+ boolean canOccludePresentation) {
+ return this;
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ef0ac33..8261dee 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1254,6 +1254,11 @@
verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
captor.getValue().getIntent().getPackage());
+
+ mService.cancelScheduledTimeoutLocked(r);
+ verify(mAlarmManager).cancel(captor.capture());
+ assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
+ captor.getValue().getIntent().getPackage());
}
@Test
@@ -11681,7 +11686,7 @@
// style + self managed call - bypasses block
when(mTelecomManager.isInSelfManagedCall(
- r.getSbn().getPackageName(), r.getUser())).thenReturn(true);
+ r.getSbn().getPackageName(), r.getUser(), true)).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
@@ -11764,7 +11769,7 @@
// style + self managed call - bypasses block
mService.clearNotifications();
reset(mUsageStats);
- when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser()))
+ when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser(), true))
.thenReturn(true);
mService.addEnqueuedNotification(r);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 670d097..130a8ca 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -66,6 +66,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.VibrationEffect;
import android.os.Vibrator;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
@@ -114,11 +115,13 @@
NotificationManager.IMPORTANCE_UNSPECIFIED);
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
- private static final long[] CUSTOM_VIBRATION = new long[] {
+ private static final long[] CUSTOM_NOTIFICATION_VIBRATION = new long[] {
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 };
- private static final long[] CUSTOM_CHANNEL_VIBRATION = new long[] {300, 400, 300, 400 };
+ private static final long[] CUSTOM_CHANNEL_VIBRATION_PATTERN = new long[] {300, 400, 300, 400 };
+ private static final VibrationEffect CUSTOM_CHANNEL_VIBRATION_EFFECT =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
private static final Uri CUSTOM_SOUND = Settings.System.DEFAULT_ALARM_ALERT_URI;
private static final AudioAttributes CUSTOM_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
@@ -135,6 +138,7 @@
MockitoAnnotations.initMocks(this);
when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator);
+ when(mVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
final Resources res = mContext.getResources();
when(mMockContext.getResources()).thenReturn(res);
when(mMockContext.getPackageManager()).thenReturn(mPm);
@@ -168,8 +172,8 @@
if (defaultVibration) {
defaults |= Notification.DEFAULT_VIBRATE;
} else {
- builder.setVibrate(CUSTOM_VIBRATION);
- channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION);
+ builder.setVibrate(CUSTOM_NOTIFICATION_VIBRATION);
+ channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION_PATTERN);
}
}
if (lights) {
@@ -217,26 +221,39 @@
return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid);
}
+ private StatusBarNotification getNotification(
+ long[] channelVibrationPattern,
+ VibrationEffect channelVibrationEffect,
+ boolean insistent) {
+ if (channelVibrationPattern != null) {
+ channel.setVibrationPattern(channelVibrationPattern);
+ } else if (channelVibrationEffect != null) {
+ channel.setVibrationEffect(channelVibrationEffect);
+ }
- private StatusBarNotification getInsistentNotification(boolean defaultVibration) {
final Builder builder = new Builder(mMockContext)
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setPriority(Notification.PRIORITY_HIGH);
- int defaults = 0;
- if (defaultVibration) {
- defaults |= Notification.DEFAULT_VIBRATE;
- } else {
- builder.setVibrate(CUSTOM_VIBRATION);
- channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION);
- }
- builder.setDefaults(defaults);
- builder.setFlag(Notification.FLAG_INSISTENT, true);
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setVibrate(CUSTOM_NOTIFICATION_VIBRATION)
+ .setFlag(Notification.FLAG_INSISTENT, insistent);
Notification n = builder.build();
return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid);
}
+ private StatusBarNotification getNotification(
+ VibrationEffect channelVibrationEffect, boolean insistent) {
+ return getNotification(
+ /* channelVibrationPattern= */ null, channelVibrationEffect, insistent);
+ }
+
+ private StatusBarNotification getNotification(
+ long[] channelVibrationPattern, boolean insistent) {
+ return getNotification(
+ channelVibrationPattern, /* channelVibrationEffect= */ null, insistent);
+ }
+
private StatusBarNotification getMessagingStyleNotification() {
return getMessagingStyleNotification(mPkg);
}
@@ -344,7 +361,7 @@
NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(VibratorHelper.createWaveformVibration(
- CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration());
+ CUSTOM_NOTIFICATION_VIBRATION, /* insistent= */ false), record.getVibration());
}
@Test
@@ -358,30 +375,137 @@
NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertNotEquals(VibratorHelper.createWaveformVibration(
- CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration());
+ CUSTOM_NOTIFICATION_VIBRATION, /* insistent= */ false), record.getVibration());
}
@Test
- public void testVibration_custom_upgradeUsesChannel() {
+ public void testVibration_customPattern_nonInsistent_usesCustomPattern() {
channel.enableVibration(true);
- // post upgrade, custom vibration.
- StatusBarNotification sbn = getNotification(PKG_O, false /* noisy */,
- false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */,
- false /* lights */, false /* defaultLights */, null /* group */);
+ StatusBarNotification sbn = getNotification(
+ CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ false);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(VibratorHelper.createWaveformVibration(
- CUSTOM_CHANNEL_VIBRATION, /* insistent= */ false), record.getVibration());
+ CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ false), record.getVibration());
}
@Test
- public void testVibration_insistent_createsInsistentVibrationEffect() {
+ public void testVibration_customPattern_insistent_createsInsistentEffect() {
channel.enableVibration(true);
- StatusBarNotification sbn = getInsistentNotification(false /* defaultBuzz */);
+ StatusBarNotification sbn = getNotification(
+ CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ true);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(VibratorHelper.createWaveformVibration(
- CUSTOM_CHANNEL_VIBRATION, /* insistent= */ true), record.getVibration());
+ CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ true), record.getVibration());
+ }
+
+ @Test
+ public void testVibration_customEffect_flagNotEnabled_usesDefaultEffect() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ channel.enableVibration(true);
+ StatusBarNotification sbn = getNotification(
+ CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ false);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ VibrationEffect effect = record.getVibration();
+ assertNotEquals(effect, CUSTOM_CHANNEL_VIBRATION_EFFECT);
+ assertNotNull(effect);
+ }
+
+ @Test
+ public void testVibration_customEffect_effectNotSupported_usesDefaultEffect() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ when(mVibrator.areVibrationFeaturesSupported(any())).thenReturn(false);
+ StatusBarNotification sbn = getNotification(
+ CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ false);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ VibrationEffect effect = record.getVibration();
+ assertNotEquals(effect, CUSTOM_CHANNEL_VIBRATION_EFFECT);
+ assertNotNull(effect);
+ }
+
+ @Test
+ public void testVibration_customNonRepeatingEffect_nonInsistent_usesCustomEffect() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ StatusBarNotification sbn = getNotification(
+ CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ false);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertEquals(CUSTOM_CHANNEL_VIBRATION_EFFECT, record.getVibration());
+ }
+
+ @Test
+ public void testVibration_customNonRepeatingEffect_insistent_createsInsistentEffect() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ StatusBarNotification sbn = getNotification(
+ CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ true);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ VibrationEffect repeatingEffect =
+ CUSTOM_CHANNEL_VIBRATION_EFFECT
+ .applyRepeatingIndefinitely(true, /* loopDelayMs= */ 0);
+ assertEquals(repeatingEffect, record.getVibration());
+ }
+
+ @Test
+ public void testVibration_customRepeatingEffect_nonInsistent_createsNonRepeatingCustomEffect() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ VibrationEffect repeatingEffect =
+ CUSTOM_CHANNEL_VIBRATION_EFFECT
+ .applyRepeatingIndefinitely(true, /* loopDelayMs= */ 0);
+ StatusBarNotification sbn = getNotification(repeatingEffect, /* insistent= */ false);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertEquals(CUSTOM_CHANNEL_VIBRATION_EFFECT, record.getVibration());
+ }
+
+ @Test
+ public void testVibration_customRepeatingEffect_insistent_usesCustomEffect() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ VibrationEffect repeatingEffect =
+ CUSTOM_CHANNEL_VIBRATION_EFFECT
+ .applyRepeatingIndefinitely(true, /* loopDelayMs= */ 0);
+ StatusBarNotification sbn = getNotification(repeatingEffect, /* insistent= */ true);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertEquals(repeatingEffect, record.getVibration());
+ }
+
+ @Test
+ public void testVibration_noCustomVibration_vibrationEnabled_usesDefaultVibration() {
+ channel.enableVibration(true);
+ StatusBarNotification sbn = getNotification(
+ /* channelVibrationPattern= */ null,
+ /* channelVibrationEffect= */ null,
+ /* insistent= */ false);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertNotNull(record.getVibration());
+ }
+
+ @Test
+ public void testVibration_noCustomVibration_vibrationNotEnabled_usesNoVibration() {
+ channel.enableVibration(false);
+ StatusBarNotification sbn = getNotification(
+ /* channelVibrationPattern= */ null,
+ /* channelVibrationEffect= */ null,
+ /* insistent= */ false);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertNull(record.getVibration());
+ }
+
+ @Test
+ public void testVibration_customVibration_vibrationNotEnabled_usesNoVibration() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+ StatusBarNotification sbn = getNotification(
+ CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ false);
+ channel.enableVibration(false);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertNull(record.getVibration());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5ddac03..8b55778 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -391,6 +391,7 @@
assertEquals(expected.getSound(), actual.getSound());
assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(expected.getVibrationEffect(), actual.getVibrationEffect());
assertEquals(expected.getGroup(), actual.getGroup());
assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
assertEquals(expected.getLightColor(), actual.getLightColor());
@@ -410,6 +411,7 @@
assertEquals(parent.getSound(), actual.getSound());
assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(parent.getVibrationEffect(), actual.getVibrationEffect());
assertEquals(parent.getGroup(), actual.getGroup());
assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
assertEquals(parent.getLightColor(), actual.getLightColor());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 74aabe1..aa9c0c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -94,7 +94,6 @@
import com.android.server.wm.TaskOrganizerController.PendingTaskEvent;
import com.android.window.flags.Flags;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -153,13 +152,6 @@
return createTask(mDisplayContent);
}
- @Before
- public void setUp() {
- // We defer callbacks since we need to adjust task surface visibility, but for these tests,
- // just run the callbacks synchronously
- mWm.mAtmService.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer((r) -> r.run());
- }
-
@Test
public void testAppearVanish() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 0ee78e3..28e03bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1791,7 +1791,6 @@
TestStartingWindowOrganizer(ActivityTaskManagerService service) {
mAtm = service;
mWMService = mAtm.mWindowManager;
- mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run);
mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index a2105b0..1df6cf7 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,6 +21,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -56,6 +57,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.IVideoCallback;
import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.flags.Flags;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -1015,9 +1017,11 @@
/**
* Connection event used to communicate a {@link android.telephony.CallQuality} report from
* telephony to Telecom for relaying to
- * {@link DiagnosticCall#onCallQualityReceived(CallQuality)}.
+ * {@link CallDiagnostics#onCallQualityReceived(CallQuality)}.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public static final String EVENT_CALL_QUALITY_REPORT =
"android.telecom.event.CALL_QUALITY_REPORT";
@@ -1026,6 +1030,8 @@
* {@link android.telephony.CallQuality} data.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public static final String EXTRA_CALL_QUALITY_REPORT =
"android.telecom.extra.CALL_QUALITY_REPORT";
@@ -4019,9 +4025,12 @@
}
/**
+ * Retrieves the direction of this connection.
* @return The direction of the call.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public final @Call.Details.CallDirection int getCallDirection() {
return mCallDirection;
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 2c6e1e4..f8e8529 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2752,17 +2752,23 @@
*
* @param packageName the package name of the app to check calls for.
* @param userHandle the user handle on which to check for calls.
+ * @param hasCrossUserAccess indicates if calls should be detected across all users.
* @return {@code true} if there are ongoing calls, {@code false} otherwise.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ Manifest.permission.INTERACT_ACROSS_USERS
+ })
public boolean isInSelfManagedCall(@NonNull String packageName,
- @NonNull UserHandle userHandle) {
+ @NonNull UserHandle userHandle, boolean hasCrossUserAccess) {
ITelecomService service = getTelecomService();
if (service != null) {
try {
return service.isInSelfManagedCall(packageName, userHandle,
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), hasCrossUserAccess);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
e.rethrowFromSystemServer();
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index f1bfd22..412e827 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -395,7 +395,7 @@
* @see TelecomServiceImpl#isInSelfManagedCall
*/
boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
- String callingPackage);
+ String callingPackage, boolean hasCrossUserAccess);
/**
* @see TelecomServiceImpl#addCall
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 287aa65..7607c64 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -15,4 +15,4 @@
per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com
#Domain Selection is jointly owned, add additional owners for domain selection specific files
-per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com
+per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index 5434c82..5f1bc87 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -67,8 +67,14 @@
assertEquals("keyCharacterMap not equal", keyCharacterMap, outKeyCharacterMap);
for (int j = 0; j < device.getMotionRanges().size(); j++) {
- assertMotionRangeEquals(device.getMotionRanges().get(j),
- outDevice.getMotionRanges().get(j));
+ InputDevice.MotionRange motionRange = device.getMotionRanges().get(j);
+ assertMotionRangeEquals(motionRange, outDevice.getMotionRanges().get(j));
+
+ int axis = motionRange.getAxis();
+ int source = motionRange.getSource();
+ assertEquals(
+ device.getViewBehavior().shouldSmoothScroll(axis, source),
+ outDevice.getViewBehavior().shouldSmoothScroll(axis, source));
}
}
@@ -93,7 +99,8 @@
.setHasBattery(true)
.setKeyboardLanguageTag("en-US")
.setKeyboardLayoutType("qwerty")
- .setUsiVersion(new HostUsiVersion(2, 0));
+ .setUsiVersion(new HostUsiVersion(2, 0))
+ .setShouldSmoothScroll(true);
for (int i = 0; i < 30; i++) {
deviceBuilder.addMotionRange(
diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h
index 5d3abc6..9276402 100644
--- a/tools/aapt/ApkBuilder.h
+++ b/tools/aapt/ApkBuilder.h
@@ -44,7 +44,7 @@
android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs);
/**
- * Adds a file to be written to the final APK. It's name must not collide
+ * Adds a file to be written to the final APK. Its name must not collide
* with that of any files previously added. When a Split APK is being
* generated, duplicates can exist as long as they are in different splits
* (resources.arsc, AndroidManifest.xml).
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 2f2ef92..66a0510 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -187,7 +187,7 @@
" be loaded alongside the base APK at runtime.\n"
" --feature-of\n"
" Builds a split APK that is a feature of the apk specified here. Resources\n"
- " in the base APK can be referenced from the the feature APK.\n"
+ " in the base APK can be referenced from the feature APK.\n"
" --feature-after\n"
" An app can have multiple Feature Split APKs which must be totally ordered.\n"
" If --feature-of is specified, this flag specifies which Feature Split APK\n"