Merge "Using proc state to determine foreground status."
diff --git a/api/current.txt b/api/current.txt
index 18f150f..e2ea649 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5245,8 +5245,8 @@
method public android.app.Notification clone();
method public int describeContents();
method public boolean getAllowSystemGeneratedContextualActions();
- method public android.app.PendingIntent getAppOverlayIntent();
method public int getBadgeIconType();
+ method public android.app.Notification.BubbleMetadata getBubbleMetadata();
method public java.lang.String getChannelId();
method public java.lang.String getGroup();
method public int getGroupAlertBehavior();
@@ -5459,6 +5459,25 @@
method public android.app.Notification.BigTextStyle setSummaryText(java.lang.CharSequence);
}
+ public static final class Notification.BubbleMetadata implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getDesiredHeight();
+ method public android.graphics.drawable.Icon getIcon();
+ method public android.app.PendingIntent getIntent();
+ method public java.lang.CharSequence getTitle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR;
+ }
+
+ public static class Notification.BubbleMetadata.Builder {
+ ctor public Notification.BubbleMetadata.Builder();
+ method public android.app.Notification.BubbleMetadata build();
+ method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int);
+ method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon);
+ method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent);
+ method public android.app.Notification.BubbleMetadata.Builder setTitle(java.lang.CharSequence);
+ }
+
public static class Notification.Builder {
ctor public Notification.Builder(android.content.Context, java.lang.String);
ctor public deprecated Notification.Builder(android.content.Context);
@@ -5478,9 +5497,9 @@
method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
method public android.app.Notification.Builder setAllowSystemGeneratedContextualActions(boolean);
- method public android.app.Notification.Builder setAppOverlayIntent(android.app.PendingIntent);
method public android.app.Notification.Builder setAutoCancel(boolean);
method public android.app.Notification.Builder setBadgeIconType(int);
+ method public android.app.Notification.Builder setBubbleMetadata(android.app.Notification.BubbleMetadata);
method public android.app.Notification.Builder setCategory(java.lang.String);
method public android.app.Notification.Builder setChannelId(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
@@ -5695,8 +5714,8 @@
public final class NotificationChannel implements android.os.Parcelable {
ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
+ method public boolean canBubble();
method public boolean canBypassDnd();
- method public boolean canOverlayApps();
method public boolean canShowBadge();
method public int describeContents();
method public void enableLights(boolean);
@@ -5712,7 +5731,7 @@
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public boolean hasUserSetImportance();
- method public void setAllowAppOverlay(boolean);
+ method public void setAllowBubbles(boolean);
method public void setBypassDnd(boolean);
method public void setDescription(java.lang.String);
method public void setGroup(java.lang.String);
@@ -5746,7 +5765,7 @@
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
- method public boolean areAppOverlaysAllowed();
+ method public boolean areBubblesAllowed();
method public boolean areNotificationsEnabled();
method public boolean canNotifyAsPackage(java.lang.String);
method public void cancel(int);
@@ -25457,6 +25476,7 @@
method public java.lang.Object setAuxEffectSendLevel(float);
method public java.lang.Object setDataSource(android.media.DataSourceDesc);
method public java.lang.Object setDisplay(android.view.SurfaceHolder);
+ method public void setDrmEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.DrmEventCallback);
method public java.lang.Object setNextDataSource(android.media.DataSourceDesc);
method public java.lang.Object setNextDataSources(java.util.List<android.media.DataSourceDesc>);
method public java.lang.Object setPlaybackParams(android.media.PlaybackParams);
@@ -25535,6 +25555,33 @@
field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
}
+ public static class MediaPlayer2.DrmEventCallback {
+ ctor public MediaPlayer2.DrmEventCallback();
+ method public void onDrmConfig(android.media.MediaPlayer2, android.media.DataSourceDesc, android.media.MediaDrm);
+ method public android.media.MediaPlayer2.DrmPreparationInfo onDrmInfo(android.media.MediaPlayer2, android.media.DataSourceDesc, android.media.MediaPlayer2.DrmInfo);
+ method public byte[] onDrmKeyRequest(android.media.MediaPlayer2, android.media.DataSourceDesc, android.media.MediaDrm.KeyRequest);
+ method public void onDrmPrepared(android.media.MediaPlayer2, android.media.DataSourceDesc, int, byte[]);
+ }
+
+ public static final class MediaPlayer2.DrmInfo {
+ method public java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public java.util.List<java.util.UUID> getSupportedSchemes();
+ }
+
+ public static final class MediaPlayer2.DrmPreparationInfo {
+ }
+
+ public static final class MediaPlayer2.DrmPreparationInfo.Builder {
+ ctor public MediaPlayer2.DrmPreparationInfo.Builder();
+ method public android.media.MediaPlayer2.DrmPreparationInfo build();
+ method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setInitData(byte[]);
+ method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setKeySetId(byte[]);
+ method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setKeyType(int);
+ method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setMimeType(java.lang.String);
+ method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setOptionalParameters(java.util.Map<java.lang.String, java.lang.String>);
+ method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setUuid(java.util.UUID);
+ }
+
public static class MediaPlayer2.EventCallback {
ctor public MediaPlayer2.EventCallback();
method public void onCallCompleted(android.media.MediaPlayer2, android.media.DataSourceDesc, int, int);
@@ -25562,6 +25609,10 @@
field public static final java.lang.String WIDTH = "android.media.mediaplayer.width";
}
+ public static final class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor public MediaPlayer2.NoDrmSchemeException(java.lang.String);
+ }
+
public static class MediaPlayer2.TrackInfo {
method public android.media.MediaFormat getFormat();
method public java.lang.String getLanguage();
diff --git a/api/system-current.txt b/api/system-current.txt
index bac7cda..276be9d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4863,6 +4863,8 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract int onCountPermissionApps(java.util.List<java.lang.String>, boolean, boolean);
method public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(java.lang.String);
+ method public abstract void onGetRuntimePermissionsBackup(android.os.UserHandle, java.io.OutputStream);
+ method public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onPermissionUsageResult(boolean, long);
method public abstract void onRevokeRuntimePermission(java.lang.String, java.lang.String);
method public abstract java.util.Map<java.lang.String, java.util.List<java.lang.String>> onRevokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>, boolean, int, java.lang.String);
field public static final java.lang.String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
@@ -4891,11 +4893,12 @@
public final class RuntimePermissionUsageInfo implements android.os.Parcelable {
ctor public RuntimePermissionUsageInfo(java.lang.CharSequence, int);
method public int describeContents();
- method public java.lang.CharSequence getName();
method public int getAppAccessCount();
+ method public java.lang.CharSequence getName();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionUsageInfo> CREATOR;
}
+
}
package android.permissionpresenterservice {
@@ -7251,7 +7254,6 @@
method public void callSessionInviteParticipantsRequestDelivered();
method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo);
method public void callSessionMayHandover(int, int);
- method public void callSessionRttAudioIndicatorChanged(android.telephony.ims.ImsStreamMediaProfile);
method public void callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase);
method public void callSessionMergeFailed(android.telephony.ims.ImsReasonInfo);
method public void callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
@@ -7262,6 +7264,7 @@
method public void callSessionResumeFailed(android.telephony.ims.ImsReasonInfo);
method public void callSessionResumeReceived(android.telephony.ims.ImsCallProfile);
method public void callSessionResumed(android.telephony.ims.ImsCallProfile);
+ method public void callSessionRttAudioIndicatorChanged(android.telephony.ims.ImsStreamMediaProfile);
method public void callSessionRttMessageReceived(java.lang.String);
method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile);
method public void callSessionRttModifyResponseReceived(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index fb50fa19..71f02b9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -945,6 +945,11 @@
field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR;
}
+ public final class MessageQueue {
+ method public int postSyncBarrier();
+ method public void removeSyncBarrier(int);
+ }
+
public final class NativeHandle implements java.io.Closeable {
ctor public NativeHandle();
ctor public NativeHandle(java.io.FileDescriptor, boolean);
@@ -957,11 +962,6 @@
method public boolean hasSingleFileDescriptor();
}
- public final class MessageQueue {
- method public int postSyncBarrier();
- method public void removeSyncBarrier(int);
- }
-
public final class PowerManager {
method public int getPowerSaveMode();
method public boolean setDynamicPowerSavings(boolean, int);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 0fa7cff..f4a1715 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -182,6 +182,7 @@
DataStallEvent data_stall_event = 121;
RescuePartyResetReported rescue_party_reset_reported = 122;
SignedConfigReported signed_config_reported = 123;
+ GnssNiEventReported gnss_ni_event_reported = 124;
}
// Pulled events will start at field 10000.
@@ -3902,3 +3903,63 @@
// Which key was used to verify the config.
optional Key verified_with = 5;
}
+
+/*
+ * Logs GNSS Network-Initiated (NI) location events.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/location/GnssLocationProvider.java
+ */
+message GnssNiEventReported {
+ // The type of GnssNiEvent.
+ enum EventType {
+ UNKNOWN = 0;
+ NI_REQUEST = 1;
+ NI_RESPONSE = 2;
+ }
+ optional EventType event_type = 1;
+
+ // An ID generated by HAL to associate NI notifications and UI responses.
+ optional int32 notification_id = 2;
+
+ // A type which distinguishes different categories of NI request, such as VOICE, UMTS_SUPL etc.
+ optional int32 ni_type = 3;
+
+ // NI requires notification.
+ optional bool need_notify = 4;
+
+ // NI requires verification.
+ optional bool need_verify = 5;
+
+ // NI requires privacy override, no notification/minimal trace.
+ optional bool privacy_override = 6;
+
+ // Timeout period to wait for user response. Set to 0 for no timeout limit. Specified in
+ // seconds.
+ optional int32 timeout = 7;
+
+ // Default response when timeout.
+ optional int32 default_response = 8;
+
+ // String representing the requester of the network inititated location request.
+ optional string requestor_id = 9;
+
+ // Notification message text string representing the service(for eg. SUPL-service) who sent the
+ // network initiated location request.
+ optional string text = 10;
+
+ // requestorId decoding scheme.
+ optional int32 requestor_id_encoding = 11;
+
+ // Notification message text decoding scheme.
+ optional int32 text_encoding = 12;
+
+ // True if SUPL ES is enabled.
+ optional bool is_supl_es_enabled = 13;
+
+ // True if GNSS location is enabled.
+ optional bool is_location_enabled = 14;
+
+ // GNSS NI responses which define the response in NI structures.
+ optional int32 user_response = 15;
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 98c5a0fb..d374f1c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.os.Process.myUid;
import static java.lang.Character.MIN_VALUE;
@@ -1025,7 +1026,7 @@
*/
@Nullable private ContentCaptureManager getContentCaptureManager() {
// ContextCapture disabled for system apps
- if (getApplicationInfo().isSystemApp()) return null;
+ if (!UserHandle.isApp(myUid())) return null;
if (mContentCaptureManager == null) {
mContentCaptureManager = getSystemService(ContentCaptureManager.class);
}
@@ -1048,9 +1049,8 @@
private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
final ContentCaptureManager cm = getContentCaptureManager();
- if (cm == null) {
- return;
- }
+ if (cm == null) return;
+
switch (type) {
case CONTENT_CAPTURE_START:
//TODO(b/111276913): decide whether the InteractionSessionId should be
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 57132a7..ab8f234 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -16,6 +16,10 @@
package android.app;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+
import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager.StackInfo;
@@ -32,7 +36,6 @@
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceSession;
@@ -82,7 +85,6 @@
private boolean mOpened; // Protected by mGuard.
private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
- private Surface mTmpSurface = new Surface();
/** The ActivityView is only allowed to contain one task. */
private final boolean mSingleTaskInstance;
@@ -319,20 +321,20 @@
private class SurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
- mTmpSurface = new Surface();
if (mVirtualDisplay == null) {
initVirtualDisplay(new SurfaceSession());
if (mVirtualDisplay != null && mActivityViewCallback != null) {
mActivityViewCallback.onActivityViewReady(ActivityView.this);
}
} else {
- // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by
- // whether it has a surface. Setting a fake surface here so DisplayManager will
- // consider this display on.
- mVirtualDisplay.setSurface(mTmpSurface);
mTmpTransaction.reparent(mRootSurfaceControl,
mSurfaceView.getSurfaceControl().getHandle()).apply();
}
+
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.setDisplayState(true);
+ }
+
updateLocation();
}
@@ -346,10 +348,8 @@
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
- mTmpSurface.release();
- mTmpSurface = null;
if (mVirtualDisplay != null) {
- mVirtualDisplay.setSurface(null);
+ mVirtualDisplay.setDisplayState(false);
}
cleanTapExcludeRegion();
}
@@ -370,15 +370,11 @@
final int height = mSurfaceView.getHeight();
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
- // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by
- // whether it has a surface. Setting a fake surface here so DisplayManager will consider
- // this display on.
mVirtualDisplay = displayManager.createVirtualDisplay(
- DISPLAY_NAME + "@" + System.identityHashCode(this),
- width, height, getBaseDisplayDensity(), mTmpSurface,
- DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
+ DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
+ getBaseDisplayDensity(), null,
+ VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
if (mVirtualDisplay == null) {
Log.e(TAG, "Failed to initialize ActivityView");
return;
@@ -443,11 +439,6 @@
displayReleased = false;
}
- if (mTmpSurface != null) {
- mTmpSurface.release();
- mTmpSurface = null;
- }
-
if (displayReleased && mActivityViewCallback != null) {
mActivityViewCallback.onActivityViewDestroyed(this);
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 163be8e..199c133 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -65,9 +65,9 @@
boolean areNotificationsEnabled(String pkg);
int getPackageImportance(String pkg);
- void setAppOverlaysAllowed(String pkg, int uid, boolean allowed);
- boolean areAppOverlaysAllowed(String pkg);
- boolean areAppOverlaysAllowedForPackage(String pkg, int uid);
+ void setBubblesAllowed(String pkg, int uid, boolean allowed);
+ boolean areBubblesAllowed(String pkg);
+ boolean areBubblesAllowedForPackage(String pkg, int uid);
void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b657a91..72819cb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1276,7 +1276,7 @@
private String mShortcutId;
private CharSequence mSettingsText;
- private PendingIntent mAppOverlayIntent;
+ private BubbleMetadata mBubbleMetadata;
/** @hide */
@IntDef(prefix = { "GROUP_ALERT_" }, value = {
@@ -2278,7 +2278,7 @@
mGroupAlertBehavior = parcel.readInt();
if (parcel.readInt() != 0) {
- mAppOverlayIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+ mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
}
mAllowSystemGeneratedContextualActions = parcel.readBoolean();
@@ -2396,7 +2396,7 @@
that.mBadgeIcon = this.mBadgeIcon;
that.mSettingsText = this.mSettingsText;
that.mGroupAlertBehavior = this.mGroupAlertBehavior;
- that.mAppOverlayIntent = this.mAppOverlayIntent;
+ that.mBubbleMetadata = this.mBubbleMetadata;
that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
if (!heavy) {
@@ -2719,9 +2719,9 @@
parcel.writeInt(mGroupAlertBehavior);
- if (mAppOverlayIntent != null) {
+ if (mBubbleMetadata != null) {
parcel.writeInt(1);
- mAppOverlayIntent.writeToParcel(parcel, 0);
+ mBubbleMetadata.writeToParcel(parcel, 0);
} else {
parcel.writeInt(0);
}
@@ -3141,11 +3141,11 @@
}
/**
- * Returns the intent that will be used to display app content in a floating window over the
- * existing foreground activity.
+ * Returns the bubble metadata that will be used to display app content in a floating window
+ * over the existing foreground activity.
*/
- public PendingIntent getAppOverlayIntent() {
- return mAppOverlayIntent;
+ public BubbleMetadata getBubbleMetadata() {
+ return mBubbleMetadata;
}
/**
@@ -3508,19 +3508,18 @@
}
/**
- * Sets the intent that will be used to display app content in a floating window
- * over the existing foreground activity.
+ * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
+ * window over the existing foreground activity.
*
- * <p>This intent will be ignored unless this notification is posted to a channel that
- * allows {@link NotificationChannel#canOverlayApps() app overlays}.</p>
+ * <p>This data will be ignored unless the notification is posted to a channel that
+ * allows {@link NotificationChannel#canBubble() bubbles}.</p>
*
- * <p>Notifications with a valid and allowed app overlay intent will be displayed as
- * floating windows outside of the notification shade on unlocked devices. When a user
- * interacts with one of these windows, this app overlay intent will be invoked and
- * displayed.</p>
+ * <b>Notifications with a valid and allowed bubble metadata will display in collapsed state
+ * outside of the notification shade on unlocked devices. When a user interacts with the
+ * collapsed state, the bubble intent will be invoked and displayed.</b>
*/
- public Builder setAppOverlayIntent(PendingIntent intent) {
- mN.mAppOverlayIntent = intent;
+ public Builder setBubbleMetadata(BubbleMetadata data) {
+ mN.mBubbleMetadata = data;
return this;
}
@@ -8422,6 +8421,186 @@
}
}
+ /**
+ * Encapsulates the information needed to display a notification as a bubble.
+ *
+ * <p>A bubble is used to display app content in a floating window over the existing
+ * foreground activity. A bubble has a collapsed state represented by an icon,
+ * {@link BubbleMetadata.Builder#setIcon(Icon)} and an expanded state which is populated
+ * via {@link BubbleMetadata.Builder#setIntent(PendingIntent)}.</p>
+ *
+ * <b>Notifications with a valid and allowed bubble will display in collapsed state
+ * outside of the notification shade on unlocked devices. When a user interacts with the
+ * collapsed bubble, the bubble intent will be invoked and displayed.</b>
+ *
+ * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
+ */
+ public static final class BubbleMetadata implements Parcelable {
+
+ private PendingIntent mPendingIntent;
+ private CharSequence mTitle;
+ private Icon mIcon;
+ private int mDesiredHeight;
+
+ private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) {
+ mPendingIntent = intent;
+ mTitle = title;
+ mIcon = icon;
+ mDesiredHeight = height;
+ }
+
+ private BubbleMetadata(Parcel in) {
+ mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
+ mTitle = in.readCharSequence();
+ mIcon = Icon.CREATOR.createFromParcel(in);
+ mDesiredHeight = in.readInt();
+ }
+
+ /**
+ * @return the pending intent used to populate the floating window for this bubble.
+ */
+ public PendingIntent getIntent() {
+ return mPendingIntent;
+ }
+
+ /**
+ * @return the title that will appear along with the app content defined by
+ * {@link #getIntent()} for this bubble.
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * @return the icon that will be displayed for this bubble when it is collapsed.
+ */
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * @return the ideal height for the floating window that app content defined by
+ * {@link #getIntent()} for this bubble.
+ */
+ public int getDesiredHeight() {
+ return mDesiredHeight;
+ }
+
+ public static final Parcelable.Creator<BubbleMetadata> CREATOR =
+ new Parcelable.Creator<BubbleMetadata>() {
+
+ @Override
+ public BubbleMetadata createFromParcel(Parcel source) {
+ return new BubbleMetadata(source);
+ }
+
+ @Override
+ public BubbleMetadata[] newArray(int size) {
+ return new BubbleMetadata[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ mPendingIntent.writeToParcel(out, 0);
+ out.writeCharSequence(mTitle);
+ mIcon.writeToParcel(out, 0);
+ out.writeInt(mDesiredHeight);
+ }
+
+ /**
+ * Builder to construct a {@link BubbleMetadata} object.
+ */
+ public static class Builder {
+
+ private PendingIntent mPendingIntent;
+ private CharSequence mTitle;
+ private Icon mIcon;
+ private int mDesiredHeight;
+
+ /**
+ * Constructs a new builder object.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets the intent that will be used when the bubble is expanded. This will display the
+ * app content in a floating window over the existing foreground activity.
+ */
+ public BubbleMetadata.Builder setIntent(PendingIntent intent) {
+ if (intent == null) {
+ throw new IllegalArgumentException("Bubble requires non-null pending intent");
+ }
+ mPendingIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the title that will appear along with the app content for this bubble.
+ *
+ * <p>A title is required and should expect to fit on a single line and make sense when
+ * shown with the content defined by {@link #setIntent(PendingIntent)}.</p>
+ */
+ public BubbleMetadata.Builder setTitle(CharSequence title) {
+ if (TextUtils.isEmpty(title)) {
+ throw new IllegalArgumentException("Bubbles require non-null or empty title");
+ }
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets the icon that will represent the bubble when it is collapsed.
+ *
+ * <p>An icon is required and should be representative of the content within the bubble.
+ * If your app produces multiple bubbles, the image should be unique for each of them.
+ * </p>
+ */
+ public BubbleMetadata.Builder setIcon(Icon icon) {
+ if (icon == null) {
+ throw new IllegalArgumentException("Bubbles require non-null icon");
+ }
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the desired height for the app content defined by
+ * {@link #setIntent(PendingIntent)}, this height may not be respected if there is not
+ * enough space on the screen or if the provided height is too small to be useful.
+ */
+ public BubbleMetadata.Builder setDesiredHeight(int height) {
+ mDesiredHeight = Math.max(height, 0);
+ return this;
+ }
+
+ /**
+ * Creates the {@link BubbleMetadata} defined by this builder.
+ * <p>Will throw {@link IllegalStateException} if required fields have not been set
+ * on this builder.</p>
+ */
+ public BubbleMetadata build() {
+ if (mPendingIntent == null) {
+ throw new IllegalStateException("Must supply pending intent to bubble");
+ }
+ if (TextUtils.isEmpty(mTitle)) {
+ throw new IllegalStateException("Must supply a title for the bubble");
+ }
+ if (mIcon == null) {
+ throw new IllegalStateException("Must supply an icon for the bubble");
+ }
+ return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight);
+ }
+ }
+ }
+
+
// When adding a new Style subclass here, don't forget to update
// Builder.getNotificationStyleClass.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 950e9aa..e95d62f 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -85,7 +85,7 @@
private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
private static final String ATT_GROUP = "group";
private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
- private static final String ATT_ALLOW_APP_OVERLAY = "app_overlay";
+ private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
private static final String DELIMITER = ",";
/**
@@ -121,7 +121,7 @@
/**
* @hide
*/
- public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 0x00000100;
+ public static final int USER_LOCKED_ALLOW_BUBBLE = 0x00000100;
/**
* @hide
@@ -134,7 +134,7 @@
USER_LOCKED_VIBRATION,
USER_LOCKED_SOUND,
USER_LOCKED_SHOW_BADGE,
- USER_LOCKED_ALLOW_APP_OVERLAY
+ USER_LOCKED_ALLOW_BUBBLE
};
private static final int DEFAULT_LIGHT_COLOR = 0;
@@ -144,7 +144,7 @@
NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_DELETED = false;
private static final boolean DEFAULT_SHOW_BADGE = true;
- private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
+ private static final boolean DEFAULT_ALLOW_BUBBLE = true;
@UnsupportedAppUsage
private final String mId;
@@ -168,7 +168,7 @@
private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
// If this is a blockable system notification channel.
private boolean mBlockableSystem = false;
- private boolean mAllowAppOverlay = DEFAULT_ALLOW_APP_OVERLAY;
+ private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
private boolean mImportanceLockedByOEM;
/**
@@ -231,7 +231,7 @@
mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null;
mLightColor = in.readInt();
mBlockableSystem = in.readBoolean();
- mAllowAppOverlay = in.readBoolean();
+ mAllowBubbles = in.readBoolean();
mImportanceLockedByOEM = in.readBoolean();
}
@@ -285,7 +285,7 @@
}
dest.writeInt(mLightColor);
dest.writeBoolean(mBlockableSystem);
- dest.writeBoolean(mAllowAppOverlay);
+ dest.writeBoolean(mAllowBubbles);
dest.writeBoolean(mImportanceLockedByOEM);
}
@@ -480,7 +480,7 @@
/**
* Sets whether notifications posted to this channel can appear outside of the notification
- * shade, floating over other apps' content.
+ * shade, floating over other apps' content as a bubble.
*
* <p>This value will be ignored for channels that aren't allowed to pop on screen (that is,
* channels whose {@link #getImportance() importance} is <
@@ -488,10 +488,10 @@
*
* <p>Only modifiable before the channel is submitted to
* * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.</p>
- * @see Notification#getAppOverlayIntent()
+ * @see Notification#getBubbleMetadata()
*/
- public void setAllowAppOverlay(boolean allowAppOverlay) {
- mAllowAppOverlay = allowAppOverlay;
+ public void setAllowBubbles(boolean allowBubbles) {
+ mAllowBubbles = allowBubbles;
}
/**
@@ -610,16 +610,16 @@
* Returns whether notifications posted to this channel can display outside of the notification
* shade, in a floating window on top of other apps.
*/
- public boolean canOverlayApps() {
- return isAppOverlayAllowed() && getImportance() >= IMPORTANCE_HIGH;
+ public boolean canBubble() {
+ return isBubbleAllowed() && getImportance() >= IMPORTANCE_HIGH;
}
/**
- * Like {@link #canOverlayApps()}, but only checks the permission, not the importance.
+ * Like {@link #canBubble()}, but only checks the permission, not the importance.
* @hide
*/
- public boolean isAppOverlayAllowed() {
- return mAllowAppOverlay;
+ public boolean isBubbleAllowed() {
+ return mAllowBubbles;
}
/**
@@ -719,7 +719,7 @@
lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
- setAllowAppOverlay(safeBool(parser, ATT_ALLOW_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY));
+ setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
}
@Nullable
@@ -838,8 +838,8 @@
if (isBlockableSystem()) {
out.attribute(null, ATT_BLOCKABLE_SYSTEM, Boolean.toString(isBlockableSystem()));
}
- if (canOverlayApps() != DEFAULT_ALLOW_APP_OVERLAY) {
- out.attribute(null, ATT_ALLOW_APP_OVERLAY, Boolean.toString(canOverlayApps()));
+ if (canBubble() != DEFAULT_ALLOW_BUBBLE) {
+ out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble()));
}
out.endTag(null, TAG_CHANNEL);
@@ -883,7 +883,7 @@
record.put(ATT_DELETED, Boolean.toString(isDeleted()));
record.put(ATT_GROUP, getGroup());
record.put(ATT_BLOCKABLE_SYSTEM, isBlockableSystem());
- record.put(ATT_ALLOW_APP_OVERLAY, canOverlayApps());
+ record.put(ATT_ALLOW_BUBBLE, canBubble());
return record;
}
@@ -983,7 +983,7 @@
&& mShowBadge == that.mShowBadge
&& isDeleted() == that.isDeleted()
&& isBlockableSystem() == that.isBlockableSystem()
- && mAllowAppOverlay == that.mAllowAppOverlay
+ && mAllowBubbles == that.mAllowBubbles
&& Objects.equals(getId(), that.getId())
&& Objects.equals(getName(), that.getName())
&& Objects.equals(mDesc, that.mDesc)
@@ -1000,7 +1000,7 @@
getLockscreenVisibility(), getSound(), mLights, getLightColor(),
getUserLockedFields(),
isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
- getAudioAttributes(), isBlockableSystem(), mAllowAppOverlay,
+ getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
mImportanceLockedByOEM);
result = 31 * result + Arrays.hashCode(mVibration);
return result;
@@ -1028,7 +1028,7 @@
+ ", mGroup='" + mGroup + '\''
+ ", mAudioAttributes=" + mAudioAttributes
+ ", mBlockableSystem=" + mBlockableSystem
- + ", mAllowAppOverlay=" + mAllowAppOverlay
+ + ", mAllowBubbles=" + mAllowBubbles
+ ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ '}';
pw.println(prefix + output);
@@ -1055,7 +1055,7 @@
+ ", mGroup='" + mGroup + '\''
+ ", mAudioAttributes=" + mAudioAttributes
+ ", mBlockableSystem=" + mBlockableSystem
- + ", mAllowAppOverlay=" + mAllowAppOverlay
+ + ", mAllowBubbles=" + mAllowBubbles
+ ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ '}';
}
@@ -1090,7 +1090,7 @@
mAudioAttributes.writeToProto(proto, NotificationChannelProto.AUDIO_ATTRIBUTES);
}
proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
- proto.write(NotificationChannelProto.ALLOW_APP_OVERLAY, mAllowAppOverlay);
+ proto.write(NotificationChannelProto.ALLOW_APP_OVERLAY, mAllowBubbles);
proto.end(token);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index aad3253..43614fe 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1080,14 +1080,14 @@
* notification shade, floating over other apps' content.
*
* <p>This value will be ignored for notifications that are posted to channels that do not
- * allow app overlays ({@link NotificationChannel#canOverlayApps()}.
+ * allow bubbles ({@link NotificationChannel#canBubble()}.
*
- * @see Notification#getAppOverlayIntent()
+ * @see Notification#getBubbleMetadata()
*/
- public boolean areAppOverlaysAllowed() {
+ public boolean areBubblesAllowed() {
INotificationManager service = getService();
try {
- return service.areAppOverlaysAllowed(mContext.getPackageName());
+ return service.areBubblesAllowed(mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 636a70f..b845673 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5555,26 +5555,24 @@
}
/**
- * @deprecated This function no longer does anything; it was an old
- * approach to managing preferred activities, which has been superseded
- * by (and conflicts with) the modern activity-based preferences.
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
public abstract void addPackageToPreferred(String packageName);
/**
- * @deprecated This function no longer does anything; it was an old
- * approach to managing preferred activities, which has been superseded
- * by (and conflicts with) the modern activity-based preferences.
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
public abstract void removePackageFromPreferred(String packageName);
/**
- * @deprecated This function no longer does anything; it was an old
- * approach to managing preferred activities, which has been superseded
- * by (and conflicts with) the modern activity-based preferences.
- *
* Retrieve the list of all currently configured preferred packages. The
* first package on the list is the most preferred, the last is the least
* preferred.
@@ -5582,15 +5580,16 @@
* @param flags Additional option flags to modify the data returned.
* @return A List of PackageInfo objects, one for each preferred
* application, in order of preference.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags);
/**
- * @deprecated This is a protected API that should not have been available
- * to third party applications. It is the platform's responsibility for
- * assigning preferred activities and this cannot be directly modified.
- *
* Add a new preferred activity mapping to the system. This will be used
* to automatically select the given activity component when
* {@link Context#startActivity(Intent) Context.startActivity()} finds
@@ -5604,20 +5603,26 @@
* this preference was made.
* @param activity The component name of the activity that is to be
* preferred.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
public abstract void addPreferredActivity(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity);
/**
- * @deprecated This is a protected API that should not have been available
- * to third party applications. It is the platform's responsibility for
- * assigning preferred activities and this cannot be directly modified.
- *
* Same as {@link #addPreferredActivity(IntentFilter, int,
ComponentName[], ComponentName)}, but with a specific userId to apply the preference
to.
* @hide
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
@UnsupportedAppUsage
@@ -5627,10 +5632,6 @@
}
/**
- * @deprecated This is a protected API that should not have been available
- * to third party applications. It is the platform's responsibility for
- * assigning preferred activities and this cannot be directly modified.
- *
* Replaces an existing preferred activity mapping to the system, and if that were not present
* adds a new preferred activity. This will be used
* to automatically select the given activity component when
@@ -5645,7 +5646,13 @@
* this preference was made.
* @param activity The component name of the activity that is to be
* preferred.
+ *
* @hide
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
@UnsupportedAppUsage
@@ -5653,10 +5660,6 @@
ComponentName[] set, ComponentName activity);
/**
- * @deprecated This is a protected API that should not have been available
- * to third party applications. It is the platform's responsibility for
- * assigning preferred activities and this cannot be directly modified.
- *
* Replaces an existing preferred activity mapping to the system, and if that were not present
* adds a new preferred activity. This will be used to automatically select the given activity
* component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple
@@ -5671,6 +5674,11 @@
* @param activity The component name of the activity that is to be preferred.
*
* @hide
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
@SystemApi
@@ -5681,6 +5689,11 @@
/**
* @hide
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
@UnsupportedAppUsage
@@ -5690,10 +5703,6 @@
}
/**
- * @deprecated This function no longer does anything; it was an old
- * approach to managing preferred activities, which has been superseded
- * by (and conflicts with) the modern activity-based preferences.
- *
* Remove all preferred activity mappings, previously added with
* {@link #addPreferredActivity}, from the
* system whose activities are implemented in the given package name.
@@ -5701,15 +5710,16 @@
*
* @param packageName The name of the package whose preferred activity
* mappings are to be removed.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
public abstract void clearPackagePreferredActivities(String packageName);
/**
- * @deprecated This function no longer does anything; it was an old
- * approach to managing preferred activities, which has been superseded
- * by (and conflicts with) the modern activity-based preferences.
- *
* Retrieve all preferred activities, previously added with
* {@link #addPreferredActivity}, that are
* currently registered with the system.
@@ -5725,6 +5735,11 @@
* @return Returns the total number of registered preferred activities
* (the number of distinct IntentFilter records, not the number of unique
* activity components) that were found.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}.
*/
@Deprecated
public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0fc50c6..1ac0ab6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2656,6 +2656,23 @@
}
/**
+ * Matches a given {@code targetCode} against a set of release codeNames. Target codes can
+ * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form
+ * {@code [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}).
+ */
+ private static boolean matchTargetCode(@NonNull String[] codeNames,
+ @NonNull String targetCode) {
+ final String targetCodeName;
+ final int targetCodeIdx = targetCode.indexOf('.');
+ if (targetCodeIdx == -1) {
+ targetCodeName = targetCode;
+ } else {
+ targetCodeName = targetCode.substring(0, targetCodeIdx);
+ }
+ return ArrayUtils.contains(codeNames, targetCodeName);
+ }
+
+ /**
* Computes the targetSdkVersion to use at runtime. If the package is not
* compatible with this platform, populates {@code outError[0]} with an
* error message.
@@ -2698,7 +2715,7 @@
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
- if (ArrayUtils.contains(platformSdkCodenames, targetCode) || forceCurrentDev) {
+ if (matchTargetCode(platformSdkCodenames, targetCode) || forceCurrentDev) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
@@ -2831,7 +2848,7 @@
// If it's a pre-release SDK and the codename matches this platform, we
// definitely meet the minimum SDK requirement.
- if (ArrayUtils.contains(platformSdkCodenames, minCode)) {
+ if (matchTargetCode(platformSdkCodenames, minCode)) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index cda8498..7e45441 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -436,6 +436,7 @@
public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
try {
mDm.setVirtualDisplaySurface(token, surface);
+ setVirtualDisplayState(token, surface != null);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -458,6 +459,14 @@
}
}
+ void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) {
+ try {
+ mDm.setVirtualDisplayState(token, isOn);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
/**
* Gets the stable device display size, in pixels.
*/
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 2d81cdf..aae8afb 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -82,6 +82,9 @@
// No permissions required but must be same Uid as the creator.
void releaseVirtualDisplay(in IVirtualDisplayCallback token);
+ // No permissions required but must be same Uid as the creator.
+ void setVirtualDisplayState(in IVirtualDisplayCallback token, boolean isOn);
+
// Get a stable metric for the device's display size. No permissions required.
Point getStableDisplaySize();
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index d354666..bf62c95 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -104,6 +104,18 @@
}
}
+ /**
+ * Sets the on/off state for a virtual display.
+ *
+ * @param isOn Whether the display should be on or off.
+ * @hide
+ */
+ public void setDisplayState(boolean isOn) {
+ if (mToken != null) {
+ mGlobal.setVirtualDisplayState(mToken, isOn);
+ }
+ }
+
@Override
public String toString() {
return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
diff --git a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
index d382eb9..bdd5ab6 100644
--- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
+++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
@@ -15,10 +15,12 @@
*/
package android.hardware.hdmi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -56,6 +58,21 @@
mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler;
}
+ /**
+ * Callback interface used to get the set System Audio Mode result.
+ *
+ * @hide
+ */
+ // TODO(b/110094868): unhide and add @SystemApi for Q
+ public interface SetSystemAudioModeCallback {
+ /**
+ * Called when the input was changed.
+ *
+ * @param result the result of the set System Audio Mode
+ */
+ void onComplete(int result);
+ }
+
/** @hide */
// TODO(b/110094868): unhide and add @SystemApi for Q
@Override
@@ -117,4 +134,34 @@
mPendingReportAudioStatus = true;
}
}
+
+ /**
+ * Set System Audio Mode on/off with audio system device.
+ *
+ * @param state true to set System Audio Mode on. False to set off.
+ * @param callback callback offer the setting result.
+ *
+ * @hide
+ */
+ // TODO(b/110094868): unhide and add @SystemApi for Q
+ public void setSystemAudioMode(boolean state, @NonNull SetSystemAudioModeCallback callback) {
+ // TODO(amyjojo): implement this when needed.
+ }
+
+ /**
+ * When device is switching to an audio only source, this method is called to broadcast
+ * a setSystemAudioMode on message to the HDMI CEC system without querying Active Source or
+ * TV supporting System Audio Control or not. This is to get volume control passthrough
+ * from STB even if TV does not support it.
+ *
+ * @hide
+ */
+ // TODO(b/110094868): unhide and add @SystemApi for Q
+ public void setSystemAudioModeOnForAudioOnlySource() {
+ try {
+ mService.setSystemAudioModeOnForAudioOnlySource();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to set System Audio Mode on for Audio Only source");
+ }
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index f5d288e..be8009e 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -432,6 +432,19 @@
}
/**
+ * Get the physical address of the device.
+ *
+ * @hide
+ */
+ public int getPhysicalAddress() {
+ try {
+ return mService.getPhysicalAddress();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Listener used to get hotplug event from HDMI port.
*/
public interface HotplugEventListener {
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 2b8d00b..66bb084 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -50,6 +50,7 @@
List<HdmiPortInfo> getPortInfo();
boolean canChangeSystemAudioMode();
boolean getSystemAudioMode();
+ int getPhysicalAddress();
void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback);
void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
@@ -73,4 +74,5 @@
void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener);
void setStandbyMode(boolean isStandbyModeOn);
void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
+ void setSystemAudioModeOnForAudioOnlySource();
}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 7a7bd83..249b622 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -18,6 +18,8 @@
import android.os.RemoteCallback;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
/**
* Interface for system apps to communication with the permission controller.
@@ -27,6 +29,7 @@
oneway interface IPermissionController {
void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
String callerPackageName, in RemoteCallback callback);
+ void getRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
void getAppPermissions(String packageName, in RemoteCallback callback);
void revokeRuntimePermission(String packageName, String permissionName);
void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 0865b62..bfcca7c 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -36,10 +36,12 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -51,6 +53,11 @@
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.util.Preconditions;
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -58,6 +65,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Interface for communicating with the permission controller.
@@ -118,6 +126,20 @@
}
/**
+ * Callback for delivering the result of {@link #getRuntimePermissionBackup}.
+ *
+ * @hide
+ */
+ public interface OnGetRuntimePermissionBackupCallback {
+ /**
+ * The result for {@link #getRuntimePermissionBackup}.
+ *
+ * @param backup The backup file
+ */
+ void onGetRuntimePermissionsBackup(@NonNull byte[] backup);
+ }
+
+ /**
* Callback for delivering the result of {@link #getAppPermissions}.
*
* @hide
@@ -219,6 +241,26 @@
}
/**
+ * Create a backup of the runtime permissions.
+ *
+ * @param user The user to be backed up
+ * @param executor Executor on which to invoke the callback
+ * @param callback Callback to receive the result
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+ public void getRuntimePermissionBackup(@NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnGetRuntimePermissionBackupCallback callback) {
+ checkNotNull(executor);
+ checkNotNull(callback);
+
+ sRemoteService.scheduleRequest(new PendingGetRuntimePermissionBackup(sRemoteService,
+ user, executor, callback));
+ }
+
+ /**
* Gets the runtime permissions for an app.
*
* @param packageName The package for which to query.
@@ -355,6 +397,89 @@
}
/**
+ * Task to read a large amount of data from a remote service.
+ */
+ private static class FileReaderTask<Callback extends Consumer<byte[]>>
+ extends AsyncTask<Void, Void, byte[]> {
+ private ParcelFileDescriptor mLocalPipe;
+ private ParcelFileDescriptor mRemotePipe;
+
+ private final @NonNull Callback mCallback;
+
+ FileReaderTask(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ ParcelFileDescriptor[] pipe;
+ try {
+ pipe = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ Log.e(TAG, "Could not create pipe needed to get runtime permission backup", e);
+ return;
+ }
+
+ mLocalPipe = pipe[0];
+ mRemotePipe = pipe[1];
+ }
+
+ /**
+ * Get the file descriptor the remote service should write the data to.
+ *
+ * <p>Needs to be closed <u>locally</u> before the FileReader can finish.
+ *
+ * @return The file the data should be written to
+ */
+ ParcelFileDescriptor getRemotePipe() {
+ return mRemotePipe;
+ }
+
+ @Override
+ protected byte[] doInBackground(Void... ignored) {
+ ByteArrayOutputStream combinedBuffer = new ByteArrayOutputStream();
+
+ try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(mLocalPipe)) {
+ byte[] buffer = new byte[16 * 1024];
+
+ while (!isCancelled()) {
+ int numRead = in.read(buffer);
+ if (numRead == -1) {
+ break;
+ }
+
+ combinedBuffer.write(buffer, 0, numRead);
+ }
+ } catch (IOException | NullPointerException e) {
+ Log.e(TAG, "Error reading runtime permission backup", e);
+ combinedBuffer.reset();
+ }
+
+ return combinedBuffer.toByteArray();
+ }
+
+ /**
+ * Interrupt the reading of the data.
+ *
+ * <p>Needs to be called when canceling this task as it might be hung.
+ */
+ void interruptRead() {
+ IoUtils.closeQuietly(mLocalPipe);
+ }
+
+ @Override
+ protected void onCancelled() {
+ onPostExecute(new byte[]{});
+ }
+
+ @Override
+ protected void onPostExecute(byte[] backup) {
+ IoUtils.closeQuietly(mLocalPipe);
+ mCallback.accept(backup);
+ }
+ }
+
+ /**
* Request for {@link #revokeRuntimePermissions}
*/
private static final class PendingRevokeRuntimePermissionRequest extends
@@ -441,6 +566,68 @@
}
/**
+ * Request for {@link #getRuntimePermissionBackup}
+ */
+ private static final class PendingGetRuntimePermissionBackup extends
+ AbstractRemoteService.PendingRequest<RemoteService, IPermissionController>
+ implements Consumer<byte[]> {
+ private final @NonNull FileReaderTask<PendingGetRuntimePermissionBackup> mBackupReader;
+ private final @NonNull Executor mExecutor;
+ private final @NonNull OnGetRuntimePermissionBackupCallback mCallback;
+ private final @NonNull UserHandle mUser;
+
+ private PendingGetRuntimePermissionBackup(@NonNull RemoteService service,
+ @NonNull UserHandle user, @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnGetRuntimePermissionBackupCallback callback) {
+ super(service);
+
+ mUser = user;
+ mExecutor = executor;
+ mCallback = callback;
+
+ mBackupReader = new FileReaderTask<>(this);
+ }
+
+ @Override
+ protected void onTimeout(RemoteService remoteService) {
+ mBackupReader.cancel(true);
+ mBackupReader.interruptRead();
+ }
+
+ @Override
+ public void run() {
+ mBackupReader.execute();
+
+ ParcelFileDescriptor remotePipe = mBackupReader.getRemotePipe();
+ try {
+ getService().getServiceInterface().getRuntimePermissionBackup(mUser, remotePipe);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error getting runtime permission backup", e);
+ } finally {
+ // Remote pipe end is duped by binder call. Local copy is not needed anymore
+ IoUtils.closeQuietly(remotePipe);
+ }
+ }
+
+ /**
+ * Called when the {@link #mBackupReader} finished reading the file.
+ *
+ * @param backup The data read
+ */
+ @Override
+ public void accept(byte[] backup) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onGetRuntimePermissionsBackup(backup));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ finish();
+ }
+ }
+
+ /**
* Request for {@link #getAppPermissions}
*/
private static final class PendingGetAppPermissionRequest extends
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 75d61e6..10e8c8d 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -33,11 +33,16 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
+import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.internal.util.Preconditions;
+import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -51,6 +56,7 @@
*/
@SystemApi
public abstract class PermissionControllerService extends Service {
+ private static final String LOG_TAG = PermissionControllerService.class.getSimpleName();
/**
* The {@link Intent} action that must be declared as handled by a service
@@ -83,6 +89,15 @@
@PermissionControllerManager.Reason int reason, @NonNull String callerPackageName);
/**
+ * Create a backup of the runtime permissions.
+ *
+ * @param user The user to back up
+ * @param out The stream to write the backup to
+ */
+ public abstract void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
+ @NonNull OutputStream out);
+
+ /**
* Gets the runtime permissions for an app.
*
* @param packageName The package for which to query.
@@ -163,6 +178,18 @@
}
@Override
+ public void getRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) {
+ checkNotNull(user);
+ checkNotNull(pipe);
+
+ enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+
+ mHandler.sendMessage(obtainMessage(
+ PermissionControllerService::getRuntimePermissionsBackup,
+ PermissionControllerService.this, user, pipe));
+ }
+
+ @Override
public void getAppPermissions(String packageName, RemoteCallback callback) {
checkNotNull(packageName, "packageName");
checkNotNull(callback, "callback");
@@ -237,6 +264,15 @@
callback.sendResult(result);
}
+ private void getRuntimePermissionsBackup(@NonNull UserHandle user,
+ @NonNull ParcelFileDescriptor outFile) {
+ try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(outFile)) {
+ onGetRuntimePermissionsBackup(user, out);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Could not open pipe to write backup tp", e);
+ }
+ }
+
private void getAppPermissions(@NonNull String packageName, @NonNull RemoteCallback callback) {
List<RuntimePermissionPresentationInfo> permissions = onGetAppPermissions(packageName);
if (permissions != null && !permissions.isEmpty()) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 64aa062..39c4266 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7407,6 +7407,22 @@
private static final Validator DOZE_WAKE_SCREEN_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
/**
+ * Gesture that skips media.
+ * @hide
+ */
+ public static final String SKIP_GESTURE = "skip_gesture";
+
+ private static final Validator SKIP_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
+ * Gesture that silences sound (alarms, notification, calls).
+ * @hide
+ */
+ public static final String SILENCE_GESTURE = "silence_gesture";
+
+ private static final Validator SILENCE_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
@@ -8483,6 +8499,8 @@
NOTIFICATION_NEW_INTERRUPTION_MODEL,
TRUST_AGENTS_EXTEND_UNLOCK,
LOCK_SCREEN_WHEN_TRUST_LOST,
+ SKIP_GESTURE,
+ SILENCE_GESTURE,
};
/**
@@ -8650,6 +8668,8 @@
VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR);
VALIDATORS.put(LOCK_SCREEN_CUSTOM_CLOCK_FACE, LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR);
VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
+ VALIDATORS.put(SKIP_GESTURE, SKIP_GESTURE_VALIDATOR);
+ VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
}
/**
@@ -9361,6 +9381,15 @@
"hdmi_system_audio_control_enabled";
/**
+ * Whether HDMI Routing Control feature is enabled. If enabled, the switch device will
+ * route to the correct input source on receiving Routing Control related messages. If
+ * disabled, you can only switch the input via controls on this device.
+ * @hide
+ */
+ public static final String HDMI_CEC_SWITCH_ENABLED =
+ "hdmi_cec_switch_enabled";
+
+ /**
* Whether TV will automatically turn on upon reception of the CEC command
* <Text View On> or <Image View On>. (0 = false, 1 = true)
*
@@ -11062,6 +11091,31 @@
/** {@hide} */
public static final String
BLUETOOTH_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_";
+ /**
+ * Enable/disable radio bug detection
+ *
+ * {@hide}
+ */
+ public static final String
+ ENABLE_RADIO_BUG_DETECTION = "enable_radio_bug_detection";
+
+ /**
+ * Count threshold of RIL wakelock timeout for radio bug detection
+ *
+ * {@hide}
+ */
+ public static final String
+ RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD =
+ "radio_bug_wakelock_timeout_count_threshold";
+
+ /**
+ * Count threshold of RIL system error for radio bug detection
+ *
+ * {@hide}
+ */
+ public static final String
+ RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD =
+ "radio_bug_system_error_count_threshold";
/**
* Activity manager specific settings.
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 0116961..aaba85b 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -42,6 +42,7 @@
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAugmentedAutofillManagerClient;
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
@@ -95,10 +96,10 @@
}
@Override
- public void onDestroyFillWindowRequest(int sessionId) {
+ public void onDestroyAllFillWindowsRequest() {
mHandler.sendMessage(
- obtainMessage(AugmentedAutofillService::handleOnDestroyFillWindowRequest,
- AugmentedAutofillService.this, sessionId));
+ obtainMessage(AugmentedAutofillService::handleOnDestroyAllFillWindowsRequest,
+ AugmentedAutofillService.this));
}
};
@@ -185,18 +186,21 @@
new FillCallback(proxy));
}
- private void handleOnDestroyFillWindowRequest(@NonNull int sessionId) {
- AutofillProxy proxy = null;
+ private void handleOnDestroyAllFillWindowsRequest() {
if (mAutofillProxies != null) {
- proxy = mAutofillProxies.get(sessionId);
+ final int size = mAutofillProxies.size();
+ for (int i = 0; i < size; i++) {
+ final int sessionId = mAutofillProxies.keyAt(i);
+ final AutofillProxy proxy = mAutofillProxies.valueAt(i);
+ if (proxy == null) {
+ // TODO(b/111330312): this might be fine, in which case we should logv it
+ Log.w(TAG, "No proxy for session " + sessionId);
+ return;
+ }
+ proxy.destroy();
+ }
+ mAutofillProxies.clear();
}
- if (proxy == null) {
- // TODO(b/111330312): this might be fine, in which case we should logv it
- Log.w(TAG, "No proxy for session " + sessionId);
- return;
- }
- proxy.destroy();
- mAutofillProxies.remove(sessionId);
}
private void handleOnUnbind() {
@@ -350,6 +354,16 @@
}
}
+ public void requestShowFillUi(int width, int height, Rect anchorBounds,
+ IAutofillWindowPresenter presenter) throws RemoteException {
+ mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds,
+ presenter);
+ }
+
+ public void requestHideFillUi() throws RemoteException {
+ mClient.requestHideFillUi(mSessionId, mFocusedId);
+ }
+
private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
synchronized (mLock) {
// TODO(b/111330312): should we close the popupwindow if the focused id changed?
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 33b88e42..51b0f01 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -16,22 +16,25 @@
package android.service.autofill.augmented;
import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG;
+import static android.service.autofill.augmented.AugmentedAutofillService.VERBOSE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.Dialog;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.service.autofill.augmented.PresentationParams.Area;
import android.util.Log;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
import android.view.WindowManager;
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -71,7 +74,7 @@
/** Indicates the data being shown is a physical address */
public static final long FLAG_METADATA_ADDRESS = 0x1;
- // TODO(b/111330312): add moar flags
+ // TODO(b/111330312): add more flags
/** @hide */
@LongDef(prefix = { "FLAG" }, value = {
@@ -83,8 +86,17 @@
private final Object mLock = new Object();
private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
+ private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter();
+
@GuardedBy("mLock")
- private Dialog mDialog;
+ private WindowManager mWm;
+ @GuardedBy("mLock")
+ private View mFillView;
+ @GuardedBy("mLock")
+ private boolean mShowing;
+ @GuardedBy("mLock")
+ private Rect mBounds;
@GuardedBy("mLock")
private boolean mDestroyed;
@@ -140,51 +152,28 @@
// window instead of destroying. In fact, it might be better to allocate a full window
// initially, which is transparent (and let touches get through) everywhere but in the
// rect boundaries.
- destroy();
// TODO(b/111330312): make sure all touch events are handled, window is always closed,
// etc.
- mDialog = new Dialog(rootView.getContext()) {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- FillWindow.this.destroy();
+ mWm = rootView.getContext().getSystemService(WindowManager.class);
+ mFillView = rootView;
+ // Listen to the touch outside to destroy the window when typing is detected.
+ mFillView.setOnTouchListener(
+ (view, motionEvent) -> {
+ if (motionEvent.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ if (VERBOSE) Log.v(TAG, "Outside touch detected, hiding the window");
+ hide();
+ }
+ return false;
}
- return false;
- }
- };
- mCloseGuard.open("destroy");
- final Window window = mDialog.getWindow();
- window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
- // Makes sure touch outside the dialog is received by the window behind the dialog.
- window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
- // Makes sure the touch outside the dialog is received by the dialog to dismiss it.
- window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
- // Makes sure keyboard shows up.
- window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-
- final int height = rect.bottom - rect.top;
- final int width = rect.right - rect.left;
- final WindowManager.LayoutParams windowParams = window.getAttributes();
- windowParams.gravity = Gravity.TOP | Gravity.LEFT;
- windowParams.y = rect.top + height;
- windowParams.height = height;
- windowParams.x = rect.left;
- windowParams.width = width;
-
- window.setAttributes(windowParams);
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- window.setBackgroundDrawableResource(android.R.color.transparent);
-
- mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
- final ViewGroup.LayoutParams diagParams = new ViewGroup.LayoutParams(width, height);
- mDialog.setContentView(rootView, diagParams);
-
+ );
+ mShowing = false;
+ mBounds = new Rect(area.getBounds());
if (DEBUG) {
Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
}
-
+ mDestroyed = false;
mProxy.setFillWindow(this);
return true;
}
@@ -194,36 +183,87 @@
void show() {
// TODO(b/111330312): check if updated first / throw exception
if (DEBUG) Log.d(TAG, "show()");
-
synchronized (mLock) {
checkNotDestroyedLocked();
- if (mDialog == null) {
+ if (mWm == null || mFillView == null) {
throw new IllegalStateException("update() not called yet, or already destroyed()");
}
-
- mDialog.show();
if (mProxy != null) {
+ try {
+ mProxy.requestShowFillUi(mBounds.right - mBounds.left,
+ mBounds.bottom - mBounds.top,
+ /*anchorBounds=*/ null, mFillWindowPresenter);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error requesting to show fill window", e);
+ }
mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
}
}
}
/**
+ * Hides the window.
+ *
+ * <p>The window is not destroyed and can be shown again
+ */
+ private void hide() {
+ if (DEBUG) Log.d(TAG, "hide()");
+ synchronized (mLock) {
+ checkNotDestroyedLocked();
+ if (mWm == null || mFillView == null) {
+ throw new IllegalStateException("update() not called yet, or already destroyed()");
+ }
+ if (mProxy != null && mShowing) {
+ try {
+ mProxy.requestHideFillUi();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error requesting to hide fill window", e);
+ }
+ }
+ }
+ }
+
+ private void handleShow(WindowManager.LayoutParams p) {
+ if (DEBUG) Log.d(TAG, "handleShow()");
+ synchronized (mLock) {
+ if (mWm != null && mFillView != null) {
+ p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ if (!mShowing) {
+ mWm.addView(mFillView, p);
+ mShowing = true;
+ } else {
+ mWm.updateViewLayout(mFillView, p);
+ }
+ }
+ }
+ }
+
+ private void handleHide() {
+ if (DEBUG) Log.d(TAG, "handleHide()");
+ synchronized (mLock) {
+ if (mWm != null && mFillView != null && mShowing) {
+ mWm.removeView(mFillView);
+ mShowing = false;
+ }
+ }
+ }
+
+ /**
* Destroys the window.
*
* <p>Once destroyed, this window cannot be used anymore
*/
public void destroy() {
- if (DEBUG) Log.d(TAG, "destroy(): mDestroyed=" + mDestroyed + " mDialog=" + mDialog);
-
- synchronized (this) {
- if (mDestroyed || mDialog == null) return;
-
- mDialog.dismiss();
- mDialog = null;
- if (mProxy != null) {
- mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
- }
+ if (DEBUG) {
+ Log.d(TAG,
+ "destroy(): mDestroyed=" + mDestroyed + " mShowing=" + mShowing + " mFillView="
+ + mFillView);
+ }
+ synchronized (mLock) {
+ if (mDestroyed) return;
+ hide();
+ mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
+ mDestroyed = true;
mCloseGuard.close();
}
}
@@ -250,11 +290,15 @@
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
synchronized (this) {
pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
- if (mDialog != null) {
- pw.print(prefix); pw.print("dialog: ");
- pw.println(mDialog.isShowing() ? "shown" : "hidden");
- pw.print(prefix); pw.print("window: ");
- pw.println(mDialog.getWindow().getAttributes());
+ if (mFillView != null) {
+ pw.print(prefix); pw.print("fill window: ");
+ pw.println(mShowing ? "shown" : "hidden");
+ pw.print(prefix); pw.print("fill view: ");
+ pw.println(mFillView);
+ pw.print(prefix); pw.print("mBounds: ");
+ pw.println(mBounds);
+ pw.print(prefix); pw.print("mWm: ");
+ pw.println(mWm);
}
}
}
@@ -264,4 +308,19 @@
public void close() throws Exception {
destroy();
}
+
+ private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+ @Override
+ public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
+ boolean fitsSystemWindows, int layoutDirection) {
+ if (DEBUG) Log.d(TAG, "FillWindowPresenter.show()");
+ mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
+ }
+
+ @Override
+ public void hide(Rect transitionEpicenter) {
+ if (DEBUG) Log.d(TAG, "FillWindowPresenter.hide()");
+ mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
+ }
+ }
}
diff --git a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
index b3ac2da1..fb6912a 100644
--- a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
+++ b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
@@ -36,5 +36,5 @@
in ComponentName activityComponent, in AutofillId focusedId,
in AutofillValue focusedValue, long requestTime, in IFillCallback callback);
- void onDestroyFillWindowRequest(int sessionId);
+ void onDestroyAllFillWindowsRequest();
}
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index e5e028d..1eaa3c5 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -323,15 +323,21 @@
mSessionUids.put(sessionId, uid);
onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
- final int flags = context.getFlags();
- if ((flags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_BY_FLAG_SECURE,
- mClientInterface.asBinder());
- return;
+ final int clientFlags = context.getFlags();
+ int stateFlags = 0;
+ if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
+ stateFlags |= ContentCaptureSession.STATE_FLAG_SECURE;
}
+ if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0) {
+ stateFlags |= ContentCaptureSession.STATE_BY_APP;
+ }
+ if (stateFlags == 0) {
+ stateFlags = ContentCaptureSession.STATE_ACTIVE;
+ } else {
+ stateFlags |= ContentCaptureSession.STATE_DISABLED;
- setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE,
- mClientInterface.asBinder());
+ }
+ setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
}
private void handleSendEvents(int uid,
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 93941d0..888a4c5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2997,5 +2997,23 @@
afm.post(() -> afm.autofill(sessionId, ids, values));
}
}
+
+ @Override
+ public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
+ presenter));
+ }
+ }
+
+ @Override
+ public void requestHideFillUi(int sessionId, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.requestHideFillUi(id, false));
+ }
+ }
}
}
diff --git a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
index 67cd0bf..140507c 100644
--- a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
/**
* Object running in the application process and responsible to provide the functionalities
@@ -29,6 +30,24 @@
* @hide
*/
interface IAugmentedAutofillManagerClient {
- Rect getViewCoordinates(in AutofillId id);
- void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);
+ /**
+ * Gets the coordinates of the input field view.
+ */
+ Rect getViewCoordinates(in AutofillId id);
+
+ /**
+ * Autofills the activity with the contents of the values.
+ */
+ void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);
+
+ /**
+ * Requests showing the fill UI.
+ */
+ void requestShowFillUi(int sessionId, in AutofillId id, int width, int height,
+ in Rect anchorBounds, in IAutofillWindowPresenter presenter);
+
+ /**
+ * Requests hiding the fill UI.
+ */
+ void requestHideFillUi(int sessionId, in AutofillId id);
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index ff45efd..81b2e01 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -29,12 +29,12 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicBoolean;
/*
* NOTE: all methods in this class should return right away, or do the real work in a handler
@@ -62,8 +62,10 @@
static final boolean VERBOSE = false;
static final boolean DEBUG = true; // STOPSHIP if not set to false
- @NonNull
- private final AtomicBoolean mDisabled = new AtomicBoolean();
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mDisabled;
@NonNull
private final Context mContext;
@@ -71,11 +73,16 @@
@Nullable
private final IContentCaptureManager mService;
+ // Flags used for starting session.
+ @GuardedBy("mLock")
+ private int mFlags;
+
// TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
// held at the Application level
@NonNull
private final Handler mHandler;
+ @GuardedBy("mLock")
private MainContentCaptureSession mMainSession;
/** @hide */
@@ -114,20 +121,25 @@
@NonNull
@UiThread
public MainContentCaptureSession getMainContentCaptureSession() {
- if (mMainSession == null) {
- mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
- mDisabled);
- if (VERBOSE) {
- Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+ synchronized (mLock) {
+ if (mMainSession == null) {
+ mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
+ mDisabled);
+ if (VERBOSE) {
+ Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+ }
}
+ return mMainSession;
}
- return mMainSession;
}
/** @hide */
public void onActivityStarted(@NonNull IBinder applicationToken,
@NonNull ComponentName activityComponent, int flags) {
- getMainContentCaptureSession().start(applicationToken, activityComponent, flags);
+ synchronized (mLock) {
+ mFlags |= flags;
+ getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
+ }
}
/** @hide */
@@ -173,7 +185,9 @@
* Checks whether content capture is enabled for this activity.
*/
public boolean isContentCaptureEnabled() {
- return mService != null && !mDisabled.get();
+ synchronized (mLock) {
+ return mService != null && !mDisabled;
+ }
}
/**
@@ -183,7 +197,9 @@
* it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
*/
public void setContentCaptureEnabled(boolean enabled) {
- //TODO(b/111276913): implement (need to finish / disable all sessions)
+ synchronized (mLock) {
+ mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP;
+ }
}
/**
@@ -198,20 +214,22 @@
/** @hide */
public void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.println("ContentCaptureManager");
-
- pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled.get());
- pw.print(prefix); pw.print("Context: "); pw.println(mContext);
- pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
- if (mService != null) {
- pw.print(prefix); pw.print("Service: "); pw.println(mService);
- }
- if (mMainSession != null) {
- final String prefix2 = prefix + " ";
- pw.print(prefix); pw.println("Main session:");
- mMainSession.dump(prefix2, pw);
- } else {
- pw.print(prefix); pw.println("No sessions");
+ synchronized (mLock) {
+ pw.print(prefix); pw.println("ContentCaptureManager");
+ pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+ pw.print(prefix); pw.print("Context: "); pw.println(mContext);
+ pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
+ if (mService != null) {
+ pw.print(prefix); pw.print("Service: "); pw.println(mService);
+ }
+ pw.print(prefix); pw.print("Flags: "); pw.println(mFlags);
+ if (mMainSession != null) {
+ final String prefix2 = prefix + " ";
+ pw.print(prefix); pw.println("Main session:");
+ mMainSession.dump(prefix2, pw);
+ } else {
+ pw.print(prefix); pw.println("No sessions");
+ }
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index d9a8416..2123308 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -21,6 +21,7 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.DebugUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewStructure;
@@ -56,42 +57,58 @@
*
* @hide
*/
- public static final int STATE_UNKNOWN = 0;
+ // NOTE: not prefixed by STATE_ so it's not printed on getStateAsString()
+ public static final int UNKNWON_STATE = 0x0;
/**
* Service's startSession() was called, but server didn't confirm it was created yet.
*
* @hide
*/
- public static final int STATE_WAITING_FOR_SERVER = 1;
+ public static final int STATE_WAITING_FOR_SERVER = 0x1;
/**
* Session is active.
*
* @hide
*/
- public static final int STATE_ACTIVE = 2;
+ public static final int STATE_ACTIVE = 0x2;
/**
* Session is disabled because there is no service for this user.
*
* @hide
*/
- public static final int STATE_DISABLED_NO_SERVICE = 3;
+ public static final int STATE_DISABLED = 0x4;
/**
* Session is disabled because its id already existed on server.
*
* @hide
*/
- public static final int STATE_DISABLED_DUPLICATED_ID = 4;
+ public static final int STATE_DUPLICATED_ID = 0x8;
+
+ /**
+ * Session is disabled because service is not set for user.
+ *
+ * @hide
+ */
+ public static final int STATE_NO_SERVICE = 0x10;
/**
* Session is disabled by FLAG_SECURE
*
* @hide
*/
- public static final int STATE_DISABLED_BY_FLAG_SECURE = 5;
+ public static final int STATE_FLAG_SECURE = 0x20;
+
+ /**
+ * Session is disabled manually by the specific app.
+ *
+ * @hide
+ */
+ public static final int STATE_BY_APP = 0x40;
+
private static final int INITIAL_CHILDREN_CAPACITY = 5;
@@ -110,7 +127,7 @@
@Nullable
protected final String mId;
- private int mState = STATE_UNKNOWN;
+ private int mState = UNKNWON_STATE;
// Lazily created on demand.
private ContentCaptureSessionId mContentCaptureSessionId;
@@ -382,21 +399,7 @@
*/
@NonNull
protected static String getStateAsString(int state) {
- switch (state) {
- case STATE_UNKNOWN:
- return "UNKNOWN";
- case STATE_WAITING_FOR_SERVER:
- return "WAITING_FOR_SERVER";
- case STATE_ACTIVE:
- return "ACTIVE";
- case STATE_DISABLED_NO_SERVICE:
- return "DISABLED_NO_SERVICE";
- case STATE_DISABLED_DUPLICATED_ID:
- return "DISABLED_DUPLICATED_ID";
- case STATE_DISABLED_BY_FLAG_SECURE:
- return "DISABLED_FLAG_SECURE";
- default:
- return "INVALID:" + state;
- }
+ return state + " (" + (state == UNKNWON_STATE ? "UNKNOWN"
+ : DebugUtils.flagsToString(ContentCaptureSession.class, "STATE_", state)) + ")";
}
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index a29aaf0..1d9018c 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -88,6 +88,7 @@
*/
public static final String EXTRA_BINDER = "binder";
+ // TODO(b/111276913): make sure disabled state is in sync with manager's disabled
@NonNull
private final AtomicBoolean mDisabled;
@@ -113,7 +114,7 @@
@Nullable
private DeathRecipient mDirectServiceVulture;
- private int mState = STATE_UNKNOWN;
+ private int mState = UNKNWON_STATE;
@Nullable
private IBinder mApplicationToken;
@@ -133,11 +134,11 @@
/** @hide */
protected MainContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
@Nullable IContentCaptureManager systemServerInterface,
- @NonNull AtomicBoolean disabled) {
+ @NonNull boolean disabled) {
mContext = context;
mHandler = handler;
mSystemServerInterface = systemServerInterface;
- mDisabled = disabled;
+ mDisabled = new AtomicBoolean(disabled);
}
@Override
@@ -184,7 +185,7 @@
private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
int flags) {
- if (mState != STATE_UNKNOWN) {
+ if (mState != UNKNWON_STATE) {
// TODO(b/111276913): revisit this scenario
Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
+ getStateAsString(mState));
@@ -247,17 +248,14 @@
}
}
- // TODO(b/111276913): change the resultCode to use flags so there's just one flag for
- // disabled stuff
- if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID
- || resultCode == STATE_DISABLED_BY_FLAG_SECURE) {
+ if ((mState & STATE_DISABLED) != 0) {
mDisabled.set(true);
handleResetSession(/* resetState= */ false);
} else {
mDisabled.set(false);
}
if (VERBOSE) {
- Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId
+ Log.v(TAG, "handleSessionStarted() result: id=" + mId
+ ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
+ ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
}
@@ -407,7 +405,7 @@
// clearings out.
private void handleResetSession(boolean resetState) {
if (resetState) {
- mState = STATE_UNKNOWN;
+ mState = UNKNWON_STATE;
}
// TODO(b/122454205): must reset children (which currently is owned by superclass)
@@ -496,8 +494,7 @@
}
pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
- pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" (");
- pw.print(getStateAsString(mState)); pw.println(")");
+ pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
if (mApplicationToken != null) {
pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 2f2f623..49ca378 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -91,4 +91,10 @@
// OPEN: Settings > Apps & Notifications -> Special app access -> Financial Apps Sms Access
SETTINGS_FINANCIAL_APPS_SMS_ACCESS = 1597;
+
+ // OPEN: Settings > System > Input & Gesture > Skip songs
+ SETTINGS_GESTURE_SKIP = 1624;
+
+ // OPEN: Settings > System > Input & Gesture > Silence alerts
+ SETTINGS_GESTURE_SILENCE = 1625;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 0e052fe..9f4345d 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -525,7 +525,10 @@
}
optional Zen zen = 71;
+ optional SettingProto skip_gesture_enabled = 74 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 74;
+ // Next tag = 76;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d40f01c..2e3bd7c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3708,4 +3708,10 @@
<!-- If device supports corner radius on windows.
This should be turned off on low-end devices to improve animation performance. -->
<bool name="config_supportsRoundedCornersOnWindows">true</bool>
+
+ <!-- If the sensor that skips media is available or not. -->
+ <bool name="config_skipSensorAvailable">false</bool>
+
+ <!-- If the sensor that silences alerts is available or not. -->
+ <bool name="config_silenceSensorAvailable">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a1ef0d5..daf8b44 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3536,4 +3536,7 @@
<java-symbol type="string" name="dynamic_mode_notification_title" />
<java-symbol type="string" name="dynamic_mode_notification_summary" />
<java-symbol type="drawable" name="ic_battery" />
+
+ <java-symbol type="bool" name="config_skipSensorAvailable" />
+ <java-symbol type="bool" name="config_silenceSensorAvailable" />
</resources>
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 267267e..3f91a65 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -51,6 +51,12 @@
private static final String PRE_RELEASE = "B";
private static final String NEWER_PRE_RELEASE = "C";
+ // Codenames with a fingerprint attached to them. These may only be present in the apps
+ // declared min SDK and not as platform codenames.
+ private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "A.fingerprint";
+ private static final String PRE_RELEASE_WITH_FINGERPRINT = "B.fingerprint";
+ private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "C.fingerprint";
+
private static final String[] CODENAMES_RELEASED = { /* empty */ };
private static final String[] CODENAMES_PRE_RELEASE = { PRE_RELEASE };
@@ -68,7 +74,7 @@
isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
outError);
- assertEquals(result, expectedMinSdk);
+ assertEquals("Error msg: " + outError[0], expectedMinSdk, result);
if (expectedMinSdk == -1) {
assertNotNull(outError[0]);
@@ -98,6 +104,7 @@
// APP: Pre-release API 10
// DEV: Pre-release API 20
verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
+ verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
// Do allow same pre-release minSdkVersion on pre-release platform,
// but overwrite the specified version with CUR_DEVELOPMENT.
@@ -105,11 +112,15 @@
// DEV: Pre-release API 20
verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
Build.VERSION_CODES.CUR_DEVELOPMENT);
+ verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
+
// Don't allow newer pre-release minSdkVersion on pre-release platform.
// APP: Pre-release API 30
// DEV: Pre-release API 20
verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
+ verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
}
@Test
@@ -133,16 +144,20 @@
// APP: Pre-release API 10
// DEV: Released API 20
verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
+ verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
// Don't allow same pre-release minSdkVersion on released platform.
// APP: Pre-release API 20
// DEV: Released API 20
verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
+ verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+
// Don't allow newer pre-release minSdkVersion on released platform.
// APP: Pre-release API 30
// DEV: Released API 20
verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
+ verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
}
private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
@@ -189,6 +204,9 @@
// DEV: Pre-release API 20
verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1,
false /* forceCurrentDev */);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1,
+ false /* forceCurrentDev */);
+
// Do allow same pre-release targetSdkVersion on pre-release platform,
// but overwrite the specified version with CUR_DEVELOPMENT.
@@ -196,18 +214,26 @@
// DEV: Pre-release API 20
verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
Build.VERSION_CODES.CUR_DEVELOPMENT, false /* forceCurrentDev */);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT, false /* forceCurrentDev */);
+
// Don't allow newer pre-release targetSdkVersion on pre-release platform.
// APP: Pre-release API 30
// DEV: Pre-release API 20
verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1,
false /* forceCurrentDev */);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1,
+ false /* forceCurrentDev */);
+
// Force newer pre-release targetSdkVersion to current pre-release platform.
// APP: Pre-release API 30
// DEV: Pre-release API 20
verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
Build.VERSION_CODES.CUR_DEVELOPMENT, true /* forceCurrentDev */);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT, true /* forceCurrentDev */);
}
@Test
@@ -235,18 +261,25 @@
// DEV: Released API 20
verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1,
false /* forceCurrentDev */);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1,
+ false /* forceCurrentDev */);
// Don't allow same pre-release targetSdkVersion on released platform.
// APP: Pre-release API 20
// DEV: Released API 20
verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1,
false /* forceCurrentDev */);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1,
+ false /* forceCurrentDev */);
+
// Don't allow newer pre-release targetSdkVersion on released platform.
// APP: Pre-release API 30
// DEV: Released API 20
verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1,
false /* forceCurrentDev */);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1,
+ false /* forceCurrentDev */);
}
/**
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1ed5ce4..df4600e 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -27,7 +27,6 @@
import static java.lang.reflect.Modifier.isStatic;
import android.platform.test.annotations.Presubmit;
-import android.provider.Settings.Global;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -270,6 +269,7 @@
Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
Settings.Global.GNSS_SATELLITE_BLACKLIST,
Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
+ Settings.Global.HDMI_CEC_SWITCH_ENABLED,
Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
Settings.Global.HDMI_CONTROL_ENABLED,
@@ -554,8 +554,10 @@
Settings.Global.APPOP_HISTORY_PARAMETERS,
Settings.Global.APPOP_HISTORY_MODE,
Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
- Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS);
-
+ Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
+ Settings.Global.ENABLE_RADIO_BUG_DETECTION,
+ Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
+ Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 0dd9d09..64b3ba0 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -318,6 +318,15 @@
mMaxVolume = maxVolume;
mIsMute = isMute;
}
+
+ @Override
+ public void setSystemAudioModeOnForAudioOnlySource() {
+ }
+
+ @Override
+ public int getPhysicalAddress() {
+ return 0x0000;
+ }
}
}
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 9170d6d..68541b4 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -86,6 +86,26 @@
mCallbacks.gles.draw(mFunctor, mData, drawInfo);
}
+void WebViewFunctor::initVk(const VkFunctorInitParams& params) {
+ ATRACE_NAME("WebViewFunctor::initVk");
+ if (!mHasContext) {
+ mHasContext = true;
+ } else {
+ return;
+ }
+ mCallbacks.vk.initialize(mFunctor, mData, params);
+}
+
+void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {
+ ATRACE_NAME("WebViewFunctor::drawVk");
+ mCallbacks.vk.draw(mFunctor, mData, params);
+}
+
+void WebViewFunctor::postDrawVk() {
+ ATRACE_NAME("WebViewFunctor::postDrawVk");
+ mCallbacks.vk.postDraw(mFunctor, mData);
+}
+
void WebViewFunctor::destroyContext() {
if (mHasContext) {
mHasContext = false;
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 1719ce7..2846cb1 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -42,6 +42,12 @@
void drawGl(const DrawGlInfo& drawInfo) const { mReference.drawGl(drawInfo); }
+ void initVk(const VkFunctorInitParams& params) { mReference.initVk(params); }
+
+ void drawVk(const VkFunctorDrawParams& params) { mReference.drawVk(params); }
+
+ void postDrawVk() { mReference.postDrawVk(); }
+
private:
friend class WebViewFunctor;
@@ -53,6 +59,9 @@
int id() const { return mFunctor; }
void sync(const WebViewSyncData& syncData) const;
void drawGl(const DrawGlInfo& drawInfo);
+ void initVk(const VkFunctorInitParams& params);
+ void drawVk(const VkFunctorDrawParams& params);
+ void postDrawVk();
void destroyContext();
sp<Handle> createHandle() {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6eefed9..d54275f 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -125,8 +125,6 @@
uirenderer::GlFunctorLifecycleListener* listener) {
FunctorDrawable* functorDrawable;
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
- // TODO(cblume) use VkFunctorDrawable instead of VkInteropFunctorDrawable here when the
- // interop is disabled/moved.
functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(
functor, listener, asSkCanvas());
} else {
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index 156f74a..2f8d381 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -17,6 +17,8 @@
#include "VkFunctorDrawable.h"
#include <private/hwui/DrawVkInfo.h>
+#include "renderthread/VulkanManager.h"
+#include "renderthread/RenderThread.h"
#include <GrBackendDrawableInfo.h>
#include <SkImage.h>
#include <utils/Color.h>
@@ -31,34 +33,58 @@
namespace uirenderer {
namespace skiapipeline {
-VkFunctorDrawHandler::VkFunctorDrawHandler(Functor* functor) : INHERITED(), mFunctor(functor) {}
+VkFunctorDrawHandler::VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle,
+ const SkMatrix& matrix, const SkIRect& clip,
+ const SkImageInfo& image_info)
+ : INHERITED()
+ , mFunctorHandle(functor_handle)
+ , mMatrix(matrix)
+ , mClip(clip)
+ , mImageInfo(image_info) {}
VkFunctorDrawHandler::~VkFunctorDrawHandler() {
- // TODO(cblume) Fill in the DrawVkInfo parameters.
- (*mFunctor)(DrawVkInfo::kModePostComposite, nullptr);
+ mFunctorHandle->postDrawVk();
}
void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) {
ATRACE_CALL();
+ if (!renderthread::RenderThread::isCurrent())
+ LOG_ALWAYS_FATAL("VkFunctorDrawHandler::draw not called on render thread");
GrVkDrawableInfo vulkan_info;
if (!info.getVkDrawableInfo(&vulkan_info)) {
return;
}
+ renderthread::VulkanManager& vk_manager =
+ renderthread::RenderThread::getInstance().vulkanManager();
+ mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams());
- DrawVkInfo draw_vk_info;
- // TODO(cblume) Fill in the rest of the parameters and test the actual call.
- draw_vk_info.isLayer = true;
+ SkMatrix44 mat4(mMatrix);
+ VkFunctorDrawParams params{
+ .width = mImageInfo.width(),
+ .height = mImageInfo.height(),
+ .is_layer = false, // TODO(boliu): Populate is_layer.
+ .color_space_ptr = mImageInfo.colorSpace(),
+ .clip_left = mClip.fLeft,
+ .clip_top = mClip.fTop,
+ .clip_right = mClip.fRight,
+ .clip_bottom = mClip.fBottom,
+ };
+ mat4.asColMajorf(¶ms.transform[0]);
+ params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
+ params.color_attachment_index = vulkan_info.fColorAttachmentIndex;
+ params.compatible_render_pass = vulkan_info.fCompatibleRenderPass;
+ params.format = vulkan_info.fFormat;
- (*mFunctor)(DrawVkInfo::kModeComposite, &draw_vk_info);
+ mFunctorHandle->drawVk(params);
+
+ vulkan_info.fDrawBounds->offset.x = mClip.fLeft;
+ vulkan_info.fDrawBounds->offset.y = mClip.fTop;
+ vulkan_info.fDrawBounds->extent.width = mClip.fRight - mClip.fLeft;
+ vulkan_info.fDrawBounds->extent.height = mClip.fBottom - mClip.fTop;
}
VkFunctorDrawable::~VkFunctorDrawable() {
- if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) {
- if (lp->listener) {
- lp->listener->onGlFunctorReleased(lp->functor);
- }
- }
}
void VkFunctorDrawable::onDraw(SkCanvas* /*canvas*/) {
@@ -67,16 +93,17 @@
}
std::unique_ptr<FunctorDrawable::GpuDrawHandler> VkFunctorDrawable::onSnapGpuDrawHandler(
- GrBackendApi backendApi, const SkMatrix& matrix) {
+ GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip,
+ const SkImageInfo& image_info) {
if (backendApi != GrBackendApi::kVulkan) {
return nullptr;
}
std::unique_ptr<VkFunctorDrawHandler> draw;
if (mAnyFunctor.index() == 0) {
- LOG_ALWAYS_FATAL("Not implemented");
- return nullptr;
+ return std::make_unique<VkFunctorDrawHandler>(std::get<0>(mAnyFunctor).handle, matrix, clip,
+ image_info);
} else {
- return std::make_unique<VkFunctorDrawHandler>(std::get<1>(mAnyFunctor).functor);
+ LOG_ALWAYS_FATAL("Not implemented");
}
}
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.h b/libs/hwui/pipeline/skia/VkFunctorDrawable.h
index d6fefc1..1a53c8f 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.h
@@ -32,15 +32,18 @@
*/
class VkFunctorDrawHandler : public FunctorDrawable::GpuDrawHandler {
public:
- explicit VkFunctorDrawHandler(Functor* functor);
+ VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle, const SkMatrix& matrix,
+ const SkIRect& clip, const SkImageInfo& image_info);
~VkFunctorDrawHandler() override;
void draw(const GrBackendDrawableInfo& info) override;
private:
typedef GpuDrawHandler INHERITED;
-
- Functor* mFunctor;
+ sp<WebViewFunctor::Handle> mFunctorHandle;
+ const SkMatrix mMatrix;
+ const SkIRect mClip;
+ const SkImageInfo mImageInfo;
};
/**
@@ -57,7 +60,8 @@
// SkDrawable functions:
void onDraw(SkCanvas* canvas) override;
std::unique_ptr<FunctorDrawable::GpuDrawHandler> onSnapGpuDrawHandler(
- GrBackendApi backendApi, const SkMatrix& matrix) override;
+ GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip,
+ const SkImageInfo& image_info) override;
};
} // namespace skiapipeline
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index fd824bd..abc4dbf 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -17,80 +17,61 @@
#ifndef ANDROID_HWUI_DRAW_VK_INFO_H
#define ANDROID_HWUI_DRAW_VK_INFO_H
+#include <SkColorSpace.h>
#include <vulkan/vulkan.h>
namespace android {
namespace uirenderer {
-/**
- * Structure used by VulkanRenderer::callDrawVKFunction() to pass and receive data from Vulkan
- * functors.
- */
-struct DrawVkInfo {
- // Input: current width/height of destination surface
- int width;
- int height;
+struct VkFunctorInitParams {
+ VkInstance instance;
+ VkPhysicalDevice physical_device;
+ VkDevice device;
+ VkQueue queue;
+ uint32_t graphics_queue_index;
+ uint32_t instance_version;
+ const char* const* enabled_instance_extension_names;
+ uint32_t enabled_instance_extension_names_length;
+ const char* const* enabled_device_extension_names;
+ uint32_t enabled_device_extension_names_length;
+ const VkPhysicalDeviceFeatures2* device_features_2;
+};
- // Input: is the render target an FBO
- bool isLayer;
+struct VkFunctorDrawParams {
+ // Input: current width/height of destination surface.
+ int width;
+ int height;
- // Input: current transform matrix, in OpenGL format
- float transform[16];
+ // Input: is the render target a FBO
+ bool is_layer;
- // Input: WebView should do its main compositing draws into this. It cannot do anything that
- // would require stopping the render pass.
- VkCommandBuffer secondaryCommandBuffer;
+ // Input: current transform matrix
+ float transform[16];
- // Input: The main color attachment index where secondaryCommandBuffer will eventually be
- // submitted.
- uint32_t colorAttachmentIndex;
+ // Input WebView should do its main compositing draws into this. It cannot do
+ // anything that would require stopping the render pass.
+ VkCommandBuffer secondary_command_buffer;
- // Input: A render pass which will be compatible to the one which the secondaryCommandBuffer
- // will be submitted into.
- VkRenderPass compatibleRenderPass;
+ // Input: The main color attachment index where secondary_command_buffer will
+ // eventually be submitted.
+ uint32_t color_attachment_index;
- // Input: Format of the destination surface.
- VkFormat format;
+ // Input: A render pass which will be compatible to the one which the
+ // secondary_command_buffer will be submitted into.
+ VkRenderPass compatible_render_pass;
- // Input: Color space
- const SkColorSpace* colorSpaceInfo;
+ // Input: Format of the destination surface.
+ VkFormat format;
- // Input: current clip rect
- int clipLeft;
- int clipTop;
- int clipRight;
- int clipBottom;
+ // Input: Color space.
+ const SkColorSpace* color_space_ptr;
- /**
- * Values used as the "what" parameter of the functor.
- */
- enum Mode {
- // Called once at WebView start
- kModeInit,
- // Called when things need to be re-created
- kModeReInit,
- // Notifies the app that the composite functor will be called soon. This allows WebView to
- // begin work early.
- kModePreComposite,
- // Do the actual composite work
- kModeComposite,
- // This allows WebView to begin using the previously submitted objects in future work.
- kModePostComposite,
- // Invoked every time the UI thread pushes over a frame to the render thread and the owning
- // view has a dirty display list*. This is a signal to sync any data that needs to be
- // shared between the UI thread and the render thread. During this time the UI thread is
- // blocked.
- kModeSync
- };
-
- /**
- * Values used by Vulkan functors to tell the framework what to do next.
- */
- enum Status {
- // The functor is done
- kStatusDone = 0x0,
- };
-}; // struct DrawVkInfo
+ // Input: current clip rect
+ int clip_left;
+ int clip_top;
+ int clip_right;
+ int clip_bottom;
+};
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h
index da3d06a..96da947 100644
--- a/libs/hwui/private/hwui/WebViewFunctor.h
+++ b/libs/hwui/private/hwui/WebViewFunctor.h
@@ -19,6 +19,7 @@
#include <cutils/compiler.h>
#include <private/hwui/DrawGlInfo.h>
+#include <private/hwui/DrawVkInfo.h>
namespace android::uirenderer {
@@ -52,18 +53,12 @@
// Called on RenderThread. initialize is guaranteed to happen before this call
void (*draw)(int functor, void* data, const DrawGlInfo& params);
} gles;
- // TODO: VK support. The current DrawVkInfo is monolithic and needs to be split up for
- // what params are valid on what callbacks
struct {
// Called either the first time the functor is used or the first time it's used after
// a call to onContextDestroyed.
- // void (*initialize)(int functor, const InitParams& params);
- // void (*frameStart)(int functor, /* todo: what params are actually needed for this to
- // be useful? Is this useful? */)
- // void (*draw)(int functor, const CompositeParams& params /* todo: rename - composite
- // almost always means something else, and we aren't compositing */);
- // void (*frameEnd)(int functor, const PostCompositeParams& params /* todo: same as
- // CompositeParams - rename */);
+ void (*initialize)(int functor, void* data, const VkFunctorInitParams& params);
+ void (*draw)(int functor, void* data, const VkFunctorDrawParams& params);
+ void (*postDraw)(int functor, void*);
} vk;
};
};
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index c06fadd..8bef359 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -171,6 +171,9 @@
mRenderState = new RenderState(*this);
mVkManager = new VulkanManager(*this);
mCacheManager = new CacheManager(mDisplayInfo);
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ mVkManager->initialize();
+ }
}
void RenderThread::requireGlContext() {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 5272227..1ef83fb 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -47,6 +47,10 @@
class RenderState;
class TestUtils;
+namespace skiapipeline {
+class VkFunctorDrawHandler;
+}
+
namespace renderthread {
class CanvasContext;
@@ -124,6 +128,7 @@
friend class DummyVsyncSource;
friend class android::uirenderer::TestUtils;
friend class android::uirenderer::WebViewFunctor;
+ friend class android::uirenderer::skiapipeline::VkFunctorDrawHandler;
RenderThread();
virtual ~RenderThread();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index aa7a141..6c540f6 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -34,6 +34,23 @@
namespace uirenderer {
namespace renderthread {
+static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
+ // All Vulkan structs that could be part of the features chain will start with the
+ // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
+ // so we can get access to the pNext for the next struct.
+ struct CommonVulkanHeader {
+ VkStructureType sType;
+ void* pNext;
+ };
+
+ void* pNext = features.pNext;
+ while (pNext) {
+ void* current = pNext;
+ pNext = static_cast<CommonVulkanHeader*>(current)->pNext;
+ free(current);
+ }
+}
+
#define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F)
#define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
@@ -66,6 +83,11 @@
mDevice = VK_NULL_HANDLE;
mPhysicalDevice = VK_NULL_HANDLE;
mInstance = VK_NULL_HANDLE;
+ mInstanceVersion = 0u;
+ mInstanceExtensions.clear();
+ mDeviceExtensions.clear();
+ free_features_extensions_structs(mPhysicalDeviceFeatures2);
+ mPhysicalDeviceFeatures2 = {};
}
bool VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) {
@@ -81,7 +103,6 @@
VK_MAKE_VERSION(1, 1, 0), // apiVersion
};
- std::vector<const char*> instanceExtensions;
{
GET_PROC(EnumerateInstanceExtensionProperties);
@@ -99,7 +120,7 @@
bool hasKHRSurfaceExtension = false;
bool hasKHRAndroidSurfaceExtension = false;
for (uint32_t i = 0; i < extensionCount; ++i) {
- instanceExtensions.push_back(extensions[i].extensionName);
+ mInstanceExtensions.push_back(extensions[i].extensionName);
if (!strcmp(extensions[i].extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) {
hasKHRSurfaceExtension = true;
}
@@ -120,8 +141,8 @@
&app_info, // pApplicationInfo
0, // enabledLayerNameCount
nullptr, // ppEnabledLayerNames
- (uint32_t) instanceExtensions.size(), // enabledExtensionNameCount
- instanceExtensions.data(), // ppEnabledExtensionNames
+ (uint32_t) mInstanceExtensions.size(), // enabledExtensionNameCount
+ mInstanceExtensions.data(), // ppEnabledExtensionNames
};
GET_PROC(CreateInstance);
@@ -201,7 +222,6 @@
// presentation with any native window. So just use the first one.
mPresentQueueIndex = 0;
- std::vector<const char*> deviceExtensions;
{
uint32_t extensionCount = 0;
err = mEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &extensionCount,
@@ -220,7 +240,7 @@
}
bool hasKHRSwapchainExtension = false;
for (uint32_t i = 0; i < extensionCount; ++i) {
- deviceExtensions.push_back(extensions[i].extensionName);
+ mDeviceExtensions.push_back(extensions[i].extensionName);
if (!strcmp(extensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
hasKHRSwapchainExtension = true;
}
@@ -237,8 +257,8 @@
}
return vkGetInstanceProcAddr(instance, proc_name);
};
- grExtensions.init(getProc, mInstance, mPhysicalDevice, instanceExtensions.size(),
- instanceExtensions.data(), deviceExtensions.size(), deviceExtensions.data());
+ grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
+ mInstanceExtensions.data(), mDeviceExtensions.size(), mDeviceExtensions.data());
if (!grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
this->destroy();
@@ -308,8 +328,8 @@
queueInfo, // pQueueCreateInfos
0, // layerCount
nullptr, // ppEnabledLayerNames
- (uint32_t) deviceExtensions.size(), // extensionCount
- deviceExtensions.data(), // ppEnabledExtensionNames
+ (uint32_t) mDeviceExtensions.size(), // extensionCount
+ mDeviceExtensions.data(), // ppEnabledExtensionNames
nullptr, // ppEnabledFeatures
};
@@ -351,36 +371,17 @@
return true;
}
-static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
- // All Vulkan structs that could be part of the features chain will start with the
- // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
- // so we can get access to the pNext for the next struct.
- struct CommonVulkanHeader {
- VkStructureType sType;
- void* pNext;
- };
-
- void* pNext = features.pNext;
- while (pNext) {
- void* current = pNext;
- pNext = static_cast<CommonVulkanHeader*>(current)->pNext;
- free(current);
- }
-}
-
void VulkanManager::initialize() {
if (mDevice != VK_NULL_HANDLE) {
return;
}
GET_PROC(EnumerateInstanceVersion);
- uint32_t instanceVersion = 0;
- LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
- LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
+ LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&mInstanceVersion));
+ LOG_ALWAYS_FATAL_IF(mInstanceVersion < VK_MAKE_VERSION(1, 1, 0));
GrVkExtensions extensions;
- VkPhysicalDeviceFeatures2 features;
- LOG_ALWAYS_FATAL_IF(!this->setupDevice(extensions, features));
+ LOG_ALWAYS_FATAL_IF(!this->setupDevice(extensions, mPhysicalDeviceFeatures2));
mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
@@ -397,9 +398,9 @@
backendContext.fDevice = mDevice;
backendContext.fQueue = mGraphicsQueue;
backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex;
- backendContext.fInstanceVersion = instanceVersion;
+ backendContext.fInstanceVersion = mInstanceVersion;
backendContext.fVkExtensions = &extensions;
- backendContext.fDeviceFeatures2 = &features;
+ backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = std::move(getProc);
// create the command pool for the command buffers
@@ -433,13 +434,29 @@
LOG_ALWAYS_FATAL_IF(!grContext.get());
mRenderThread.setGrContext(grContext);
- free_features_extensions_structs(features);
-
if (Properties::enablePartialUpdates && Properties::useBufferAge) {
mSwapBehavior = SwapBehavior::BufferAge;
}
}
+VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
+ return VkFunctorInitParams{
+ .instance = mInstance,
+ .physical_device = mPhysicalDevice,
+ .device = mDevice,
+ .queue = mGraphicsQueue,
+ .graphics_queue_index = mGraphicsQueueIndex,
+ .instance_version = mInstanceVersion,
+ .enabled_instance_extension_names = mInstanceExtensions.data(),
+ .enabled_instance_extension_names_length =
+ static_cast<uint32_t>(mInstanceExtensions.size()),
+ .enabled_device_extension_names = mDeviceExtensions.data(),
+ .enabled_device_extension_names_length =
+ static_cast<uint32_t>(mDeviceExtensions.size()),
+ .device_features_2 = &mPhysicalDeviceFeatures2,
+ };
+}
+
// Returns the next BackbufferInfo to use for the next draw. The function will make sure all
// previous uses have finished before returning.
VulkanSurface::BackbufferInfo* VulkanManager::getAvailableBackbuffer(VulkanSurface* surface) {
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 9eb942c..105ee09 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -132,6 +132,9 @@
// Creates a fence that is signaled, when all the pending Vulkan commands are flushed.
status_t createReleaseFence(sp<Fence>& nativeFence);
+ // Returned pointers are owned by VulkanManager.
+ VkFunctorInitParams getVkFunctorInitParams() const;
+
private:
friend class RenderThread;
@@ -234,6 +237,12 @@
VkCommandBuffer mDummyCB = VK_NULL_HANDLE;
+ // Variables saved to populate VkFunctorInitParams.
+ uint32_t mInstanceVersion = 0u;
+ std::vector<const char*> mInstanceExtensions;
+ std::vector<const char*> mDeviceExtensions;
+ VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures2{};
+
enum class SwapBehavior {
Discard,
BufferAge,
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 793aa27..5516086 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -816,7 +817,7 @@
*
* @return The audio frame size in bytes corresponding to the encoding and the channel mask.
*/
- public int getFrameSizeInBytes() {
+ public @IntRange(from = 1) int getFrameSizeInBytes() {
return mFrameSizeInBytes;
}
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index df96994..aa79c41 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -60,6 +60,7 @@
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@@ -67,6 +68,7 @@
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -399,8 +401,7 @@
clearSourceInfos();
// Modular DRM clean up
- mOnDrmConfigHelper = null;
- synchronized (mDrmEventCbLock) {
+ synchronized (mDrmEventCallbackLock) {
mDrmEventCallbackRecords.clear();
}
@@ -775,7 +776,7 @@
}
boolean hasError = false;
for (DataSourceDesc dsd : dsds) {
- if (dsd != null) {
+ if (dsd == null) {
hasError = true;
continue;
}
@@ -2889,7 +2890,7 @@
}
private void sendDrmEvent(final DrmEventNotifier notifier) {
- synchronized (mDrmEventCbLock) {
+ synchronized (mDrmEventCallbackLock) {
try {
for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
cb.first.execute(() -> notifier.notify(cb.second));
@@ -2901,12 +2902,28 @@
}
}
+ private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier)
+ throws InterruptedException, ExecutionException {
+ synchronized (mDrmEventCallbackLock) {
+ mDrmEventCallbackRecords.get(0);
+ for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+ CompletableFuture<T> ret = new CompletableFuture<>();
+ cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second)));
+ return ret.get();
+ }
+ }
+ return null;
+ }
+
private interface EventNotifier {
void notify(EventCallback callback);
}
- private interface DrmEventNotifier {
- void notify(DrmEventCallback callback);
+ private interface DrmEventNotifier<T> {
+ default void notify(DrmEventCallback callback) { }
+ default T notifyWait(DrmEventCallback callback) {
+ return null;
+ }
}
/* Do not change these values without updating their counterparts
@@ -3351,73 +3368,209 @@
// Modular DRM begin
/**
- * Interface definition of a callback to be invoked when the app
- * can do DRM configuration (get/set properties) before the session
- * is opened. This facilitates configuration of the properties, like
- * 'securityLevel', which has to be set after DRM scheme creation but
- * before the DRM session is opened.
+ * An immutable structure per {@link DataSourceDesc} with settings required to initiate a DRM
+ * protected playback session.
*
- * The only allowed DRM calls in this listener are
- * {@link MediaPlayer2#getDrmPropertyString(DataSourceDesc, String)}
- * and {@link MediaPlayer2#setDrmPropertyString(DataSourceDesc, String, String)}.
- * @hide
+ * @see DrmPreparationInfo.Builder
*/
- public interface OnDrmConfigHelper {
+ public static final class DrmPreparationInfo {
+
/**
- * Called to give the app the opportunity to configure DRM before the session is created
- *
- * @param mp the {@code MediaPlayer2} associated with this callback
- * @param dsd the DataSourceDesc of this data source
+ * Mutable builder to create a {@link MediaPlayer2.DrmPreparationInfo} object.
*/
- public void onDrmConfig(MediaPlayer2 mp, DataSourceDesc dsd);
- }
+ public static final class Builder {
- /**
- * Register a callback to be invoked for configuration of the DRM object before
- * the session is created.
- * The callback will be invoked synchronously during the execution
- * of {@link #prepareDrm}.
- *
- * @param listener the callback that will be run
- * @hide
- */
- // This is a synchronous call.
- public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
- mOnDrmConfigHelper = listener;
- }
+ private UUID mUUID;
+ private byte[] mKeySetId;
+ private byte[] mInitData;
+ private String mMimeType;
+ private int mKeyType;
+ private Map<String, String> mOptionalParameters;
- private OnDrmConfigHelper mOnDrmConfigHelper;
+ /**
+ * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved from
+ * the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
+ *
+ * @param uuid of selected crypto scheme
+ * @return this
+ */
+ public Builder setUuid(@NonNull UUID uuid) {
+ this.mUUID = uuid;
+ return this;
+ }
+
+ /**
+ * Set identifier of a persisted offline key obtained from
+ * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared(MediaPlayer2, DataSourceDesc, int, byte[])}.
+ *
+ * A {@code keySetId} can be used to restore persisted offline keys into a new playback
+ * session of a DRM protected data source. When {@code keySetId} is set, {@code initData},
+ * {@code mimeType}, {@code keyType}, {@code optionalParameters} are ignored.
+ *
+ * @param keySetId identifier of a persisted offline key
+ * @return this
+ */
+ public Builder setKeySetId(@Nullable byte[] keySetId) {
+ this.mKeySetId = keySetId;
+ return this;
+ }
+
+ /**
+ * Set container-specific DRM initialization data. Its meaning is interpreted based on
+ * {@code mimeType}. For example, it could contain the content ID, key ID or other data
+ * obtained from the content metadata that is required to generate a
+ * {@link MediaDrm.KeyRequest}.
+ *
+ * @param initData container-specific DRM initialization data
+ * @return this
+ */
+ public Builder setInitData(@Nullable byte[] initData) {
+ this.mInitData = initData;
+ return this;
+ }
+
+ /**
+ * Set mime type of the content
+ *
+ * @param mimeType mime type to the content
+ * @return this
+ */
+ public Builder setMimeType(@Nullable String mimeType) {
+ this.mMimeType = mimeType;
+ return this;
+ }
+
+ /**
+ * Set type of the key request. The request may be to acquire keys
+ * for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content,
+ * {@link MediaDrm#KEY_TYPE_OFFLINE}. Releasing previously acquired keys
+ * ({@link MediaDrm#KEY_TYPE_RELEASE}) is not allowed.
+ *
+ * @param keyType type of the key request
+ * @return this
+ */
+ public Builder setKeyType(@MediaPlayer2.MediaDrmKeyType int keyType) {
+ this.mKeyType = keyType;
+ return this;
+ }
+
+ /**
+ * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent to
+ * the license server.
+ *
+ * @param optionalParameters optional parameters to be included in a key request
+ * @return this
+ */
+ public Builder setOptionalParameters(
+ @Nullable Map<String, String> optionalParameters) {
+ this.mOptionalParameters = optionalParameters;
+ return this;
+ }
+
+ /**
+ * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the settings of this builder
+ */
+ public MediaPlayer2.DrmPreparationInfo build() {
+ return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType, mKeyType,
+ mOptionalParameters);
+ }
+
+ }
+
+ private final UUID mUUID;
+ private final byte[] mKeySetId;
+ private final byte[] mInitData;
+ private final String mMimeType;
+ private final int mKeyType;
+ private final Map<String, String> mOptionalParameters;
+
+ private DrmPreparationInfo(UUID mUUID, byte[] mKeySetId, byte[] mInitData, String mMimeType,
+ int mKeyType, Map<String, String> optionalParameters) {
+ this.mUUID = mUUID;
+ this.mKeySetId = mKeySetId;
+ this.mInitData = mInitData;
+ this.mMimeType = mMimeType;
+ this.mKeyType = mKeyType;
+ this.mOptionalParameters = optionalParameters;
+ }
+
+ }
/**
* Interface definition for callbacks to be invoked when the player has the corresponding
* DRM events.
- * @hide
*/
public static class DrmEventCallback {
/**
- * Called to indicate DRM info is available
+ * Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that
+ * bundles DRM initialization parameters.
*
* @param mp the {@code MediaPlayer2} associated with this callback
- * @param dsd the DataSourceDesc of this data source
- * @param drmInfo DRM info of the source including PSSH, and subset
- * of crypto schemes supported by this device
+ * @param dsd the {@link DataSourceDesc} of this data source
+ * @param drmInfo DRM info of the source including PSSH, and subset of crypto schemes
+ * supported by this device
+ * @return a {@link DrmPreparationInfo} object to initialize DRM playback, or null to skip
+ * DRM initialization
*/
- public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) { }
+ public DrmPreparationInfo onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) {
+ return null;
+ }
/**
- * Called to notify the client that {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}
- * is finished and ready for key request/response.
+ * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
+ * {@code dsd}
*
* @param mp the {@code MediaPlayer2} associated with this callback
- * @param dsd the DataSourceDesc of this data source
+ * @param dsd the {@link DataSourceDesc} of this data source
* @param status the result of DRM preparation.
+ * @param keySetId optional identifier that can be used to restore DRM playback initiated
+ * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
+ *
+ * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
*/
- public void onDrmPrepared(
- MediaPlayer2 mp, DataSourceDesc dsd, @PrepareDrmStatusCode int status) { }
+ public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
+ @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
+
+ /**
+ * Called to give the app the opportunity to configure DRM before the session is created.
+ *
+ * This facilitates configuration of the properties, like 'securityLevel', which
+ * has to be set after DRM scheme creation but before the DRM session is opened.
+ *
+ * The only allowed DRM calls in this listener are
+ * {@link MediaDrm#getPropertyString(String)},
+ * {@link MediaDrm#getPropertyByteArray(String)},
+ * {@link MediaDrm#setPropertyString(String, String)},
+ * {@link MediaDrm#setPropertyByteArray(String, byte[])},
+ * {@link MediaDrm#setOnExpirationUpdateListener},
+ * and {@link MediaDrm#setOnKeyStatusChangeListener}.
+ *
+ * @param mp the {@code MediaPlayer2} associated with this callback
+ * @param dsd the {@link DataSourceDesc} of this data source
+ * @param drm handle to get/set DRM properties and listeners for this data source
+ */
+ public void onDrmConfig(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
+ @NonNull MediaDrm drm) { }
+
+ /**
+ * Called to indicate the DRM session for {@code dsd} is ready for key request/response
+ *
+ * @param mp the {@code MediaPlayer2} associated with this callback
+ * @param dsd the {@link DataSourceDesc} of this data source
+ * @param request a {@link MediaDrm.KeyRequest} prepared using the
+ * {@link DrmPreparationInfo} returned from
+ * {@link #onDrmInfo(MediaPlayer2, DataSourceDesc, DrmInfo)}
+ * @return the response to {@code request} (from license server)
+ */
+ public byte[] onDrmKeyRequest(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
+ @NonNull MediaDrm.KeyRequest request) {
+ return null;
+ }
+
}
- private final Object mDrmEventCbLock = new Object();
- private ArrayList<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
+ private final Object mDrmEventCallbackLock = new Object();
+ private List<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
new ArrayList<Pair<Executor, DrmEventCallback>>();
/**
@@ -3425,10 +3578,9 @@
*
* @param eventCallback the callback that will be run
* @param executor the executor through which the callback should be invoked
- * @hide
*/
// This is a synchronous call.
- public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
+ public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull DrmEventCallback eventCallback) {
if (eventCallback == null) {
throw new IllegalArgumentException("Illegal null EventCallback");
@@ -3437,8 +3589,9 @@
throw new IllegalArgumentException(
"Illegal null Executor for the EventCallback");
}
- synchronized (mDrmEventCbLock) {
- mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
+ synchronized (mDrmEventCallbackLock) {
+ mDrmEventCallbackRecords = Collections.singletonList(
+ new Pair<Executor, DrmEventCallback>(executor, eventCallback));
}
}
@@ -3450,7 +3603,7 @@
*/
// This is a synchronous call.
public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
- synchronized (mDrmEventCbLock) {
+ synchronized (mDrmEventCallbackLock) {
for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
if (cb.second == eventCallback) {
mDrmEventCallbackRecords.remove(cb);
@@ -3564,7 +3717,7 @@
/**
* Prepares the DRM for the given data source
* <p>
- * If {@link OnDrmConfigHelper} is registered, it will be called during
+ * If {@link DrmEventCallback} is registered, it will be called during
* preparation to allow configuration of the DRM properties before opening the
* DRM session. It should be used only for a series of
* {@link #getDrmPropertyString(DataSourceDesc, String)} and
@@ -3587,8 +3740,7 @@
* @param dsd The DRM protected data source
*
* @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
- * from the source through {@link #getDrmInfo(DataSourceDesc)} or registering a
- * {@link DrmEventCallback#onDrmInfo}.
+ * from the source listening to {@link DrmEventCallback#onDrmInfo}.
*
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
* @hide
@@ -3661,8 +3813,8 @@
sendDrmEvent(new DrmEventNotifier() {
@Override
public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(
- MediaPlayer2.this, dsd, prepareDrmStatus);
+ callback.onDrmPrepared(MediaPlayer2.this, dsd, prepareDrmStatus,
+ /* TODO: keySetId */ null);
}
});
@@ -3876,7 +4028,6 @@
/**
* Encapsulates the DRM properties of the source.
- * @hide
*/
public static final class DrmInfo {
private Map<UUID, byte[]> mMapPssh;
@@ -4013,10 +4164,8 @@
}; // DrmInfo
/**
- * Thrown when a DRM method is called before preparing a DRM scheme through
- * {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}.
+ * Thrown when a DRM method is called when there is no active DRM session.
* Extends MediaDrm.MediaDrmException
- * @hide
*/
public static final class NoDrmSchemeException extends MediaDrmException {
public NoDrmSchemeException(String detailMessage) {
@@ -4291,9 +4440,9 @@
}
void prepare(UUID uuid) throws UnsupportedSchemeException,
- ResourceBusyException, NotProvisionedException {
- final OnDrmConfigHelper onDrmConfigHelper = mOnDrmConfigHelper;
- Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + onDrmConfigHelper);
+ ResourceBusyException, NotProvisionedException, InterruptedException,
+ ExecutionException {
+ Log.v(TAG, "prepareDrm: uuid: " + uuid);
synchronized (this) {
if (mActiveDrmUUID != null) {
@@ -4334,9 +4483,13 @@
} // synchronized
// call the callback outside the lock
- if (onDrmConfigHelper != null) {
- onDrmConfigHelper.onDrmConfig(MediaPlayer2.this, mDSD);
- }
+ sendDrmEventWait(new DrmEventNotifier<Void>() {
+ @Override
+ public Void notifyWait(DrmEventCallback callback) {
+ callback.onDrmConfig(MediaPlayer2.this, mDSD, mDrmObj);
+ return null;
+ }
+ });
synchronized (this) {
mDrmConfigAllowed = false;
@@ -4506,7 +4659,7 @@
@Override
public void notify(DrmEventCallback callback) {
callback.onDrmPrepared(
- MediaPlayer2.this, mDSD, finalStatus);
+ MediaPlayer2.this, mDSD, finalStatus, /* TODO: keySetId */ null);
}
});
diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h
index bb2ee9b..0490e65 100644
--- a/native/webview/plat_support/draw_fn.h
+++ b/native/webview/plat_support/draw_fn.h
@@ -74,7 +74,10 @@
VkQueue queue;
uint32_t graphics_queue_index;
uint32_t instance_version;
- const char* const* enabled_extension_names;
+ const char* const* enabled_instance_extension_names;
+ uint32_t enabled_instance_extension_names_length;
+ const char* const* enabled_device_extension_names;
+ uint32_t enabled_device_extension_names_length;
// Only one of device_features and device_features_2 should be non-null.
// If both are null then no features are enabled.
VkPhysicalDeviceFeatures* device_features;
@@ -128,15 +131,13 @@
struct AwDrawFn_PostDrawVkParams {
int version;
-
- // Input: Fence for the composite command buffer to signal it has finished its
- // work on the GPU.
- int fd;
};
// Called on render thread while UI thread is blocked. Called for both GL and
// VK.
-typedef void AwDrawFn_OnSync(int functor, void* data, AwDrawFn_OnSyncParams* params);
+typedef void AwDrawFn_OnSync(int functor,
+ void* data,
+ AwDrawFn_OnSyncParams* params);
// Called on render thread when either the context is destroyed _or_ when the
// functor's last reference goes away. Will always be called with an active
@@ -150,17 +151,24 @@
typedef void AwDrawFn_OnDestroyed(int functor, void* data);
// Only called for GL.
-typedef void AwDrawFn_DrawGL(int functor, void* data, AwDrawFn_DrawGLParams* params);
+typedef void AwDrawFn_DrawGL(int functor,
+ void* data,
+ AwDrawFn_DrawGLParams* params);
// Initialize vulkan state. Needs to be called again after any
// OnContextDestroyed. Only called for Vulkan.
-typedef void AwDrawFn_InitVk(int functor, void* data, AwDrawFn_InitVkParams* params);
+typedef void AwDrawFn_InitVk(int functor,
+ void* data,
+ AwDrawFn_InitVkParams* params);
// Only called for Vulkan.
-typedef void AwDrawFn_DrawVk(int functor, void* data, AwDrawFn_DrawVkParams* params);
+typedef void AwDrawFn_DrawVk(int functor,
+ void* data,
+ AwDrawFn_DrawVkParams* params);
// Only called for Vulkan.
-typedef void AwDrawFn_PostDrawVk(int functor, void* data,
+typedef void AwDrawFn_PostDrawVk(int functor,
+ void* data,
AwDrawFn_PostDrawVkParams* params);
struct AwDrawFnFunctorCallbacks {
@@ -183,7 +191,8 @@
typedef AwDrawFnRenderMode AwDrawFn_QueryRenderMode(void);
// Create a functor. |functor_callbacks| should be valid until OnDestroyed.
-typedef int AwDrawFn_CreateFunctor(void* data, AwDrawFnFunctorCallbacks* functor_callbacks);
+typedef int AwDrawFn_CreateFunctor(void* data,
+ AwDrawFnFunctorCallbacks* functor_callbacks);
// May be called on any thread to signal that the functor should be destroyed.
// The functor will receive an onDestroyed when the last usage of it is
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index 6c1ceab..b97bbc3 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -74,6 +74,79 @@
support->callbacks.draw_gl(functor, support->data, ¶ms);
}
+void initializeVk(int functor, void* data,
+ const uirenderer::VkFunctorInitParams& init_vk_params) {
+ SupportData* support = static_cast<SupportData*>(data);
+ VkPhysicalDeviceFeatures2 device_features_2;
+ if (init_vk_params.device_features_2)
+ device_features_2 = *init_vk_params.device_features_2;
+
+ AwDrawFn_InitVkParams params{
+ .version = kAwDrawFnVersion,
+ .instance = init_vk_params.instance,
+ .physical_device = init_vk_params.physical_device,
+ .device = init_vk_params.device,
+ .queue = init_vk_params.queue,
+ .graphics_queue_index = init_vk_params.graphics_queue_index,
+ .instance_version = init_vk_params.instance_version,
+ .enabled_instance_extension_names =
+ init_vk_params.enabled_instance_extension_names,
+ .enabled_instance_extension_names_length =
+ init_vk_params.enabled_instance_extension_names_length,
+ .enabled_device_extension_names =
+ init_vk_params.enabled_device_extension_names,
+ .enabled_device_extension_names_length =
+ init_vk_params.enabled_device_extension_names_length,
+ .device_features = nullptr,
+ .device_features_2 =
+ init_vk_params.device_features_2 ? &device_features_2 : nullptr,
+ };
+ support->callbacks.init_vk(functor, support->data, ¶ms);
+}
+
+void drawVk(int functor, void* data, const uirenderer::VkFunctorDrawParams& draw_vk_params) {
+ SupportData* support = static_cast<SupportData*>(data);
+ float gabcdef[7];
+ draw_vk_params.color_space_ptr->transferFn(gabcdef);
+ AwDrawFn_DrawVkParams params{
+ .version = kAwDrawFnVersion,
+ .width = draw_vk_params.width,
+ .height = draw_vk_params.height,
+ .is_layer = draw_vk_params.is_layer,
+ .secondary_command_buffer = draw_vk_params.secondary_command_buffer,
+ .color_attachment_index = draw_vk_params.color_attachment_index,
+ .compatible_render_pass = draw_vk_params.compatible_render_pass,
+ .format = draw_vk_params.format,
+ .transfer_function_g = gabcdef[0],
+ .transfer_function_a = gabcdef[1],
+ .transfer_function_b = gabcdef[2],
+ .transfer_function_c = gabcdef[3],
+ .transfer_function_d = gabcdef[4],
+ .transfer_function_e = gabcdef[5],
+ .transfer_function_f = gabcdef[6],
+ .clip_left = draw_vk_params.clip_left,
+ .clip_top = draw_vk_params.clip_top,
+ .clip_right = draw_vk_params.clip_right,
+ .clip_bottom = draw_vk_params.clip_bottom,
+ };
+ COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3),
+ gamut_transform_size_mismatch);
+ draw_vk_params.color_space_ptr->toXYZD50(
+ reinterpret_cast<skcms_Matrix3x3*>(¶ms.color_space_toXYZD50));
+ COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_vk_params.transform),
+ mismatched_transform_matrix_sizes);
+ for (int i = 0; i < NELEM(params.transform); ++i) {
+ params.transform[i] = draw_vk_params.transform[i];
+ }
+ support->callbacks.draw_vk(functor, support->data, ¶ms);
+}
+
+void postDrawVk(int functor, void* data) {
+ SupportData* support = static_cast<SupportData*>(data);
+ AwDrawFn_PostDrawVkParams params{.version = kAwDrawFnVersion};
+ support->callbacks.post_draw_vk(functor, support->data, ¶ms);
+}
+
int CreateFunctor(void* data, AwDrawFnFunctorCallbacks* functor_callbacks) {
static bool callbacks_initialized = false;
static uirenderer::WebViewFunctorCallbacks webview_functor_callbacks = {
@@ -82,9 +155,19 @@
.onDestroyed = &onDestroyed,
};
if (!callbacks_initialized) {
- // Under uirenderer::RenderMode::Vulkan, whether gles or vk union should
- // be populated should match whether the vk-gl interop is used.
- webview_functor_callbacks.gles.draw = &draw_gl;
+ switch (uirenderer::WebViewFunctor_queryPlatformRenderMode()) {
+ case uirenderer::RenderMode::OpenGL_ES:
+ webview_functor_callbacks.gles.draw = &draw_gl;
+ break;
+ case uirenderer::RenderMode::Vulkan:
+ webview_functor_callbacks.vk.initialize = &initializeVk;
+ webview_functor_callbacks.vk.draw = &drawVk;
+ webview_functor_callbacks.vk.postDraw = &postDrawVk;
+ // TODO(boliu): Remove this once SkiaRecordingCanvas::drawWebViewFunctor
+ // no longer uses GL interop.
+ webview_functor_callbacks.gles.draw = &draw_gl;
+ break;
+ }
callbacks_initialized = true;
}
SupportData* support = new SupportData{
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 03c6205..9e89b89 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -99,8 +99,12 @@
<string name="connected_via_network_scorer_default">Automatically connected via network rating provider</string>
<!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
<string name="connected_via_passpoint">Connected via %1$s</string>
+ <!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
+ <string name="ssid_by_passpoint_provider"><xliff:g id="ssid" example="Cafe Wifi">%1$s</xliff:g> by <xliff:g id="passpointProvider" example="Passpoint Provider">%2$s</xliff:g></string>
<!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
<string name="available_via_passpoint">Available via %1$s</string>
+ <!-- Status message of OSU Provider network when not connected. [CHAR LIMIT=NONE] -->
+ <string name="tap_to_set_up">Tap to set up</string>
<!-- Package name for Settings app-->
<string name="settings_package" translatable="false">com.android.settings</string>
<!-- Package name for Certinstaller app-->
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index af5a24f..a97931a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -41,6 +41,7 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.Bundle;
import android.os.Parcelable;
@@ -182,6 +183,10 @@
public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
+ public static final String KEY_PREFIX_AP = "AP:";
+ public static final String KEY_PREFIX_FQDN = "FQDN:";
+ public static final String KEY_PREFIX_OSU = "OSU:";
+
private final Context mContext;
private String ssid;
@@ -204,9 +209,6 @@
@Speed private int mSpeed = Speed.NONE;
private boolean mIsScoredNetworkMetered = false;
- // used to co-relate internal vs returned accesspoint.
- int mId;
-
/**
* Information associated with the {@link PasspointConfiguration}. Only maintaining
* the relevant info to preserve spaces.
@@ -215,6 +217,8 @@
private String mProviderFriendlyName;
private boolean mIsCarrierAp = false;
+
+ private OsuProvider mOsuProvider;
/**
* The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
*/
@@ -280,14 +284,18 @@
// Calculate required fields
updateKey();
updateRssi();
-
- mId = sLastId.incrementAndGet();
}
+ /**
+ * Creates an AccessPoint with only a WifiConfiguration. This is used for the saved networks
+ * page.
+ *
+ * Passpoint Credential AccessPoints should be created with this.
+ * Make sure to call setScanResults after constructing with this.
+ */
public AccessPoint(Context context, WifiConfiguration config) {
mContext = context;
loadConfig(config);
- mId = sLastId.incrementAndGet();
}
/**
@@ -298,7 +306,19 @@
mContext = context;
mFqdn = config.getHomeSp().getFqdn();
mProviderFriendlyName = config.getHomeSp().getFriendlyName();
- mId = sLastId.incrementAndGet();
+ }
+
+ /**
+ * Initialize an AccessPoint object for a Passpoint OSU Provider.
+ * Make sure to call setScanResults after constructing with this.
+ */
+ public AccessPoint(Context context, OsuProvider provider) {
+ mContext = context;
+ mOsuProvider = provider;
+ mRssi = 1;
+ // TODO: This placeholder SSID is here to avoid null pointer exceptions.
+ ssid = "<OsuProvider AP SSID goes here>";
+ updateKey();
}
AccessPoint(Context context, Collection<ScanResult> results) {
@@ -324,8 +344,6 @@
mIsCarrierAp = firstResult.isCarrierAp;
mCarrierApEapType = firstResult.carrierApEapType;
mCarrierName = firstResult.carrierName;
-
- mId = sLastId.incrementAndGet();
}
@VisibleForTesting void loadConfig(WifiConfiguration config) {
@@ -344,14 +362,19 @@
StringBuilder builder = new StringBuilder();
if (isPasspoint()) {
- builder.append(mConfig.FQDN);
- } else if (TextUtils.isEmpty(getSsidStr())) {
- builder.append(getBssid());
- } else {
- builder.append(getSsidStr());
+ builder.append(KEY_PREFIX_FQDN).append(mConfig.FQDN);
+ } else if (isOsuProvider()) {
+ builder.append(KEY_PREFIX_OSU).append(mOsuProvider.getOsuSsid());
+ builder.append(',').append(mOsuProvider.getServerUri());
+ } else { // Non-Passpoint AP
+ builder.append(KEY_PREFIX_AP);
+ if (TextUtils.isEmpty(getSsidStr())) {
+ builder.append(getBssid());
+ } else {
+ builder.append(getSsidStr());
+ }
+ builder.append(',').append(getSecurity());
}
-
- builder.append(',').append(getSecurity());
mKey = builder.toString();
}
@@ -396,8 +419,8 @@
return difference;
}
- // Sort by ssid.
- difference = getSsidStr().compareToIgnoreCase(other.getSsidStr());
+ // Sort by title.
+ difference = getTitle().compareToIgnoreCase(other.getTitle());
if (difference != 0) {
return difference;
}
@@ -595,6 +618,7 @@
public static String getKey(ScanResult result) {
StringBuilder builder = new StringBuilder();
+ builder.append(KEY_PREFIX_AP);
if (TextUtils.isEmpty(result.SSID)) {
builder.append(result.BSSID);
} else {
@@ -609,14 +633,17 @@
StringBuilder builder = new StringBuilder();
if (config.isPasspoint()) {
- builder.append(config.FQDN);
- } else if (TextUtils.isEmpty(config.SSID)) {
- builder.append(config.BSSID);
+ builder.append(KEY_PREFIX_FQDN).append(config.FQDN);
} else {
- builder.append(removeDoubleQuotes(config.SSID));
+ builder.append(KEY_PREFIX_AP);
+ if (TextUtils.isEmpty(config.SSID)) {
+ builder.append(config.BSSID);
+ } else {
+ builder.append(removeDoubleQuotes(config.SSID));
+ }
+ builder.append(',').append(getSecurity(config));
}
- builder.append(',').append(getSecurity(config));
return builder.toString();
}
@@ -839,91 +866,97 @@
public String getTitle() {
if (isPasspoint()) {
return mConfig.providerFriendlyName;
+ } else if (isOsuProvider()) {
+ return mOsuProvider.getFriendlyName();
} else {
return getSsidStr();
}
}
public String getSummary() {
- return getSettingsSummary(mConfig);
+ return getSettingsSummary();
}
public String getSettingsSummary() {
- return getSettingsSummary(mConfig);
- }
-
- private String getSettingsSummary(WifiConfiguration config) {
// Update to new summary
StringBuilder summary = new StringBuilder();
- if (isActive() && config != null && config.isPasspoint()) {
- // This is the active connection on passpoint
- summary.append(getSummary(mContext, getDetailedState(),
- false, config.providerFriendlyName));
- } else if (isActive() && config != null && getDetailedState() == DetailedState.CONNECTED
- && mIsCarrierAp) {
- summary.append(String.format(mContext.getString(R.string.connected_via_carrier), mCarrierName));
- } else if (isActive()) {
- // This is the active connection on non-passpoint network
- summary.append(getSummary(mContext, getDetailedState(),
- mInfo != null && mInfo.isEphemeral()));
- } else if (config != null && config.isPasspoint()
- && config.getNetworkSelectionStatus().isNetworkEnabled()) {
- String format = mContext.getString(R.string.available_via_passpoint);
- summary.append(String.format(format, config.providerFriendlyName));
- } else if (config != null && config.hasNoInternetAccess()) {
- int messageID = config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
- ? R.string.wifi_no_internet_no_reconnect
- : R.string.wifi_no_internet;
- summary.append(mContext.getString(messageID));
- } else if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
- WifiConfiguration.NetworkSelectionStatus networkStatus =
- config.getNetworkSelectionStatus();
- switch (networkStatus.getNetworkSelectionDisableReason()) {
- case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
- summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
- break;
- case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
- summary.append(mContext.getString(R.string.wifi_check_password_try_again));
- break;
- case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
- case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
- summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
- break;
- case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
- summary.append(mContext.getString(R.string.wifi_disabled_generic));
- break;
+ if (isActive()) {
+ if (isPasspoint()) {
+ // This is the active connection on passpoint
+ summary.append(getSummary(mContext, ssid, getDetailedState(),
+ false, mConfig.providerFriendlyName));
+ } else if (mConfig != null && getDetailedState() == DetailedState.CONNECTED
+ && mIsCarrierAp) {
+ // This is the active connection on a carrier AP
+ summary.append(String.format(mContext.getString(R.string.connected_via_carrier),
+ mCarrierName));
+ } else {
+ // This is the active connection on non-passpoint network
+ summary.append(getSummary(mContext, getDetailedState(),
+ mInfo != null && mInfo.isEphemeral()));
}
- } else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
- summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
- } else if (mIsCarrierAp) {
- summary.append(String.format(mContext.getString(R.string.available_via_carrier), mCarrierName));
- } else if (!isReachable()) { // Wifi out of range
- summary.append(mContext.getString(R.string.wifi_not_in_range));
- } else { // In range, not disabled.
- if (config != null) { // Is saved network
- // Last attempt to connect to this failed. Show reason why
- switch (config.recentFailure.getAssociationStatus()) {
- case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA:
- summary.append(mContext.getString(
- R.string.wifi_ap_unable_to_handle_new_sta));
+ } else { // not active
+ if (mConfig != null && mConfig.hasNoInternetAccess()) {
+ int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+ ? R.string.wifi_no_internet_no_reconnect
+ : R.string.wifi_no_internet;
+ summary.append(mContext.getString(messageID));
+ } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
+ WifiConfiguration.NetworkSelectionStatus networkStatus =
+ mConfig.getNetworkSelectionStatus();
+ switch (networkStatus.getNetworkSelectionDisableReason()) {
+ case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
+ summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
break;
- default:
- // "Saved"
- summary.append(mContext.getString(R.string.wifi_remembered));
+ case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
+ summary.append(mContext.getString(R.string.wifi_check_password_try_again));
break;
+ case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
+ case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
+ summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
+ break;
+ case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
+ summary.append(mContext.getString(R.string.wifi_disabled_generic));
+ break;
+ }
+ } else if (mConfig != null && mConfig.getNetworkSelectionStatus().isNotRecommended()) {
+ summary.append(mContext.getString(
+ R.string.wifi_disabled_by_recommendation_provider));
+ } else if (mIsCarrierAp) {
+ summary.append(String.format(mContext.getString(
+ R.string.available_via_carrier), mCarrierName));
+ } else if (isOsuProvider()) {
+ summary.append(mContext.getString(R.string.tap_to_set_up));
+ } else if (!isReachable()) { // Wifi out of range
+ summary.append(mContext.getString(R.string.wifi_not_in_range));
+ } else { // In range, not disabled.
+ if (mConfig != null) { // Is saved network
+ // Last attempt to connect to this failed. Show reason why
+ switch (mConfig.recentFailure.getAssociationStatus()) {
+ case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA:
+ summary.append(mContext.getString(
+ R.string.wifi_ap_unable_to_handle_new_sta));
+ break;
+ default:
+ // "Saved"
+ summary.append(mContext.getString(R.string.wifi_remembered));
+ break;
+ }
}
}
}
+
+
if (isVerboseLoggingEnabled()) {
- summary.append(WifiUtils.buildLoggingSummary(this, config));
+ summary.append(WifiUtils.buildLoggingSummary(this, mConfig));
}
- if (config != null && (WifiUtils.isMeteredOverridden(config) || config.meteredHint)) {
+ if (mConfig != null && (WifiUtils.isMeteredOverridden(mConfig) || mConfig.meteredHint)) {
return mContext.getResources().getString(
R.string.preference_summary_default_combination,
- WifiUtils.getMeteredLabel(mContext, config),
+ WifiUtils.getMeteredLabel(mContext, mConfig),
summary.toString());
}
@@ -976,6 +1009,13 @@
}
/**
+ * Return true if this AccessPoint represents an OSU Provider.
+ */
+ public boolean isOsuProvider() {
+ return mOsuProvider != null;
+ }
+
+ /**
* Return whether the given {@link WifiInfo} is for this access point.
* If the current AP does not have a network Id then the config is used to
* match based on SSID and security.
@@ -1065,8 +1105,8 @@
void setScanResults(Collection<ScanResult> scanResults) {
// Validate scan results are for current AP only by matching SSID/BSSID
- // Passpoint R1 networks are not bound to a specific SSID/BSSID, so skip this for passpoint.
- if (!isPasspoint()) {
+ // Passpoint networks are not bound to a specific SSID/BSSID, so skip this for passpoint.
+ if (!isPasspoint() && !isOsuProvider()) {
String key = getKey();
for (ScanResult result : scanResults) {
String scanResultKey = AccessPoint.getKey(result);
@@ -1119,7 +1159,17 @@
}
}
- /** Attempt to update the AccessPoint and return true if an update occurred. */
+ /**
+ * Attempt to update the AccessPoint with the current connection info.
+ * This is used to set an AccessPoint to the active one if the connection info matches, or
+ * conversely to set an AccessPoint to inactive if the connection info does not match. The RSSI
+ * is also updated upon a match. Listeners will be notified if an update occurred.
+ *
+ * This is called in {@link WifiTracker#updateAccessPoints} as well as in callbacks for handling
+ * NETWORK_STATE_CHANGED_ACTION, RSSI_CHANGED_ACTION, and onCapabilitiesChanged in WifiTracker.
+ *
+ * Returns true if an update occurred.
+ */
public boolean update(
@Nullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
@@ -1246,11 +1296,11 @@
public static String getSummary(Context context, String ssid, DetailedState state,
boolean isEphemeral, String passpointProvider) {
- if (state == DetailedState.CONNECTED && ssid == null) {
- if (TextUtils.isEmpty(passpointProvider) == false) {
+ if (state == DetailedState.CONNECTED) {
+ if (!TextUtils.isEmpty(passpointProvider)) {
// Special case for connected + passpoint networks.
- String format = context.getString(R.string.connected_via_passpoint);
- return String.format(format, passpointProvider);
+ String format = context.getString(R.string.ssid_by_passpoint_provider);
+ return String.format(format, ssid, passpointProvider);
} else if (isEphemeral) {
// Special case for connected + ephemeral networks.
final NetworkScoreManager networkScoreManager = context.getSystemService(
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 79a7240..6d28891 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -35,6 +35,7 @@
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiNetworkScoreCache.CacheListener;
+import android.net.wifi.hotspot2.OsuProvider;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
@@ -584,7 +585,7 @@
Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) {
WifiConfiguration config = pairing.first;
- // TODO: Prioritize home networks before roaming networks
+ // TODO(b/118705403): Prioritize home networks before roaming networks
List<ScanResult> scanResults = new ArrayList<>();
List<ScanResult> homeScans =
@@ -618,6 +619,23 @@
}
}
+ // Add Passpoint OSU Provider AccessPoints
+ // TODO(b/118705403): filter out OSU Providers which we already have credentials from.
+ Map<OsuProvider, List<ScanResult>> providersAndScans =
+ mWifiManager.getMatchingOsuProviders(cachedScanResults);
+ for (OsuProvider provider : providersAndScans.keySet()) {
+ AccessPoint accessPointOsu = new AccessPoint(mContext, provider);
+ // TODO(b/118705403): accessPointOsu.setScanResults(Matching ScanResult with best
+ // RSSI)
+ // TODO(b/118705403): Figure out if we would need to update an OSU AP (this will be
+ // used if we need to display it at the top of the picker as the "active" AP).
+ // Otherwise, OSU APs should ignore attempts to update the active connection
+ // info.
+ // accessPointOsu.update(connectionConfig, mLastInfo, mLastNetworkInfo);
+ accessPoints.add(accessPointOsu);
+ }
+
+
// If there were no scan results, create an AP for the currently connected network (if
// it exists).
if (accessPoints.isEmpty() && connectionConfig != null) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index bcf37ff..18bdb20 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2363,6 +2363,14 @@
SecureSettingsProto.Zen.SETTINGS_SUGGESTION_VIEWED);
p.end(zenToken);
+ dumpSetting(s, p,
+ Settings.Secure.SKIP_GESTURE,
+ SecureSettingsProto.SKIP_GESTURE_ENABLED);
+
+ dumpSetting(s, p,
+ Settings.Secure.SILENCE_GESTURE,
+ SecureSettingsProto.SILENCE_GESTURE_ENABLED);
+
// Please insert new settings using the same order as in SecureSettingsProto.
p.end(token);
diff --git a/packages/SystemUI/res-keyguard/drawable/bubble_hour_hand.xml b/packages/SystemUI/res-keyguard/drawable/bubble_hour_hand.xml
new file mode 100644
index 0000000..d3c3a51
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bubble_hour_hand.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="200dp"
+ android:width="200dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M50.082,14.199m-13.985,0a13.985,13.985 0,1 1,27.97 0a13.985,13.985 0,1 1,-27.97 0"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/bubble_minute_hand.xml b/packages/SystemUI/res-keyguard/drawable/bubble_minute_hand.xml
new file mode 100644
index 0000000..a4417fb
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bubble_minute_hand.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="200dp"
+ android:width="200dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100" >
+ <path
+ android:fillColor="#000000"
+ android:pathData="M50.082,0.025L50.082,0.025A13.985,15.63 0,0 1,64.067 15.656L64.067,49.029A13.985,15.63 0,0 1,50.082 64.659L50.082,64.659A13.985,15.63 0,0 1,36.097 49.029L36.097,15.656A13.985,15.63 0,0 1,50.082 0.025z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/bubble_clock.xml b/packages/SystemUI/res-keyguard/layout/bubble_clock.xml
new file mode 100644
index 0000000..0d72657
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/bubble_clock.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<com.android.keyguard.clock.ClockLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <TextClock
+ android:id="@+id/digital_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:letterSpacing="0.03"
+ android:singleLine="true"
+ style="@style/widget_big"
+ android:format12Hour="@string/keyguard_widget_12_hours_format"
+ android:format24Hour="@string/keyguard_widget_24_hours_format"
+ />
+ <com.android.keyguard.clock.ImageClock
+ android:id="@+id/analog_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ >
+ <ImageView
+ android:id="@+id/minute_hand"
+ android:layout_width="300dp"
+ android:layout_height="300dp"
+ android:src="@drawable/bubble_minute_hand"
+ android:tint="@color/bubbleMinuteHandColor"
+ />
+ <ImageView
+ android:id="@+id/hour_hand"
+ android:layout_width="300dp"
+ android:layout_height="300dp"
+ android:src="@drawable/bubble_hour_hand"
+ android:tint="@color/bubbleHourHandColor"
+ />
+ </com.android.keyguard.clock.ImageClock>
+</com.android.keyguard.clock.ClockLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/digital_clock.xml b/packages/SystemUI/res-keyguard/layout/digital_clock.xml
new file mode 100644
index 0000000..cf4a573
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/digital_clock.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_alignParentTop="true">
+ <TextClock
+ android:id="@+id/lock_screen_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:letterSpacing="0.03"
+ android:singleLine="true"
+ style="@style/widget_big"
+ android:format12Hour="@string/keyguard_widget_12_hours_format"
+ android:format24Hour="@string/keyguard_widget_24_hours_format" />
+ />
+</FrameLayout>
+
diff --git a/packages/SystemUI/res-keyguard/values/colors.xml b/packages/SystemUI/res-keyguard/values/colors.xml
new file mode 100644
index 0000000..7a849eb
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+ -->
+<resources>
+ <!-- Default color for hour hand of Bubble clock. -->
+ <color name="bubbleHourHandColor">#C97343</color>
+ <!-- Default color for minute hand of Bubble clock. -->
+ <color name="bubbleMinuteHandColor">#F5C983</color>
+</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
index 523720d5..1a684a0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
@@ -323,7 +323,7 @@
return null;
}
// Create our own ClassLoader so we can use our own code as the parent.
- ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName);
+ ClassLoader classLoader = mManager.getClassLoader(info);
Context pluginContext = new PluginContextWrapper(
mContext.createApplicationContext(info, 0), classLoader);
Class<?> pluginClass = Class.forName(cls, true, classLoader);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index da143f9..7139708 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -14,6 +14,7 @@
package com.android.systemui.shared.plugins;
+import android.app.LoadedApk;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
@@ -44,15 +45,17 @@
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.shared.plugins.PluginInstanceManager.PluginContextWrapper;
import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo;
import dalvik.system.PathClassLoader;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
/**
* @see Plugin
@@ -117,6 +120,7 @@
return mPluginEnabler;
}
+ // TODO(mankoff): This appears to be only called from tests. Remove?
public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
if (info == null) {
@@ -282,17 +286,25 @@
}
}
- public ClassLoader getClassLoader(String sourceDir, String pkg) {
- if (!isDebuggable && !mWhitelistedPlugins.contains(pkg)) {
- Log.w(TAG, "Cannot get class loader for non-whitelisted plugin. Src:" + sourceDir +
- ", pkg: " + pkg);
+ /** Returns class loader specific for the given plugin. */
+ public ClassLoader getClassLoader(ApplicationInfo appInfo) {
+ if (!isDebuggable && !mWhitelistedPlugins.contains(appInfo.packageName)) {
+ Log.w(TAG, "Cannot get class loader for non-whitelisted plugin. Src:"
+ + appInfo.sourceDir + ", pkg: " + appInfo.packageName);
return null;
}
- if (mClassLoaders.containsKey(pkg)) {
- return mClassLoaders.get(pkg);
+ if (mClassLoaders.containsKey(appInfo.packageName)) {
+ return mClassLoaders.get(appInfo.packageName);
}
- ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader());
- mClassLoaders.put(pkg, classLoader);
+
+ List<String> zipPaths = new ArrayList<>();
+ List<String> libPaths = new ArrayList<>();
+ LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths);
+ ClassLoader classLoader = new PathClassLoader(
+ TextUtils.join(File.pathSeparator, zipPaths),
+ TextUtils.join(File.pathSeparator, libPaths),
+ getParentClassLoader());
+ mClassLoaders.put(appInfo.packageName, classLoader);
return classLoader;
}
@@ -309,11 +321,6 @@
return mParentClassLoader;
}
- public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException {
- ClassLoader classLoader = getClassLoader(info.sourceDir, pkg);
- return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
- }
-
public <T> boolean dependsOn(Plugin p, Class<T> cls) {
for (int i = 0; i < mPluginMap.size(); i++) {
if (mPluginMap.valueAt(i).dependsOn(p, cls)) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 3cfd6a9..0ec0bf0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,9 +1,15 @@
package com.android.keyguard;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
import android.graphics.Paint;
import android.graphics.Paint.Style;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -12,6 +18,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.clock.BubbleClockController;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
@@ -19,13 +26,19 @@
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+import java.util.Objects;
import java.util.TimeZone;
import java.util.function.Consumer;
+import java.util.function.Supplier;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
public class KeyguardClockSwitch extends RelativeLayout {
+
+ private LayoutInflater mLayoutInflater;
+
+ private final ContentResolver mContentResolver;
/**
* Optional/alternative clock injected via plugin.
*/
@@ -79,12 +92,25 @@
}
};
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
+ }
+ }
+ };
+
public KeyguardClockSwitch(Context context) {
this(context, null);
}
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ mContentResolver = context.getContentResolver();
}
/**
@@ -108,7 +134,22 @@
mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
.withPlugin(ClockPlugin.class)
.withCallback(mClockPluginConsumer)
+ // Using withDefault even though this isn't the default as a workaround.
+ // ExtensionBulider doesn't provide the ability to supply a ClockPlugin
+ // instance based off of the value of a setting. Since multiple "default"
+ // can be provided, using a supplier that changes the settings value.
+ // A null return will cause Extension#reload to look at the next "default"
+ // supplier.
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ BubbleClockController.class.getName(),
+ () -> BubbleClockController.build(mLayoutInflater)))
.build();
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ false, mContentObserver);
Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
}
@@ -116,6 +157,7 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mClockExtension.destroy();
+ mContentResolver.unregisterContentObserver(mContentObserver);
Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
}
@@ -269,4 +311,44 @@
StatusBarStateController.StateListener getStateListener() {
return mStateListener;
}
+
+ /**
+ * Supplier that only gets an instance when a settings value matches expected value.
+ */
+ private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
+
+ private final ContentResolver mContentResolver;
+ private final String mKey;
+ private final String mValue;
+ private final Supplier<ClockPlugin> mSupplier;
+
+ /**
+ * Constructs a supplier that changes secure setting key against value.
+ *
+ * @param contentResolver Used to look up settings value.
+ * @param key Settings key.
+ * @param value If the setting matches this values that get supplies a ClockPlugin
+ * instance.
+ * @param supplier Supplier of ClockPlugin instance, only used if the setting
+ * matches value.
+ */
+ SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
+ Supplier<ClockPlugin> supplier) {
+ mContentResolver = contentResolver;
+ mKey = key;
+ mValue = value;
+ mSupplier = supplier;
+ }
+
+ /**
+ * Returns null if the settings value doesn't match the expected value.
+ *
+ * A null return causes Extension#reload to skip this supplier and move to the next.
+ */
+ @Override
+ public ClockPlugin get() {
+ final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
+ return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
new file mode 100644
index 0000000..db6127f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.keyguard.clock;
+
+import android.graphics.Paint.Style;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextClock;
+
+import com.android.keyguard.R;
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.TimeZone;
+
+/**
+ * Controller for Bubble clock that can appear on lock screen and AOD.
+ */
+public class BubbleClockController implements ClockPlugin {
+
+ /**
+ * Custom clock shown on AOD screen and behind stack scroller on lock.
+ */
+ private View mView;
+ private TextClock mDigitalClock;
+ private ImageClock mAnalogClock;
+
+ /**
+ * Small clock shown on lock screen above stack scroller.
+ */
+ private View mLockClockContainer;
+ private TextClock mLockClock;
+
+ /**
+ * Controller for transition to dark state.
+ */
+ private CrossFadeDarkController mDarkController;
+
+ private BubbleClockController() { }
+
+ /**
+ * Create a BubbleClockController instance.
+ *
+ * @param layoutInflater Inflater used to inflate custom clock views.
+ */
+ public static BubbleClockController build(LayoutInflater layoutInflater) {
+ BubbleClockController controller = new BubbleClockController();
+ controller.createViews(layoutInflater);
+ return controller;
+ }
+
+ private void createViews(LayoutInflater layoutInflater) {
+ mView = layoutInflater.inflate(R.layout.bubble_clock, null);
+ mDigitalClock = (TextClock) mView.findViewById(R.id.digital_clock);
+ mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock);
+
+ mLockClockContainer = layoutInflater.inflate(R.layout.digital_clock, null);
+ mLockClock = (TextClock) mLockClockContainer.findViewById(R.id.lock_screen_clock);
+ mLockClock.setVisibility(View.GONE);
+
+ mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock);
+ }
+
+ @Override
+ public View getView() {
+ return mLockClockContainer;
+ }
+
+ @Override
+ public View getBigClockView() {
+ return mView;
+ }
+
+ @Override
+ public void setStyle(Style style) {}
+
+ @Override
+ public void setTextColor(int color) {
+ mLockClock.setTextColor(color);
+ mDigitalClock.setTextColor(color);
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mAnalogClock.onTimeChanged();
+ }
+
+ @Override
+ public void setDarkAmount(float darkAmount) {
+ mDarkController.setDarkAmount(darkAmount);
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mAnalogClock.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public boolean shouldShowStatusArea() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
new file mode 100644
index 0000000..5aa5668
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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.keyguard.clock;
+
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.keyguard.R;
+
+/**
+ * Positions clock faces (analog, digital, typographic) and handles pixel shifting
+ * to prevent screen burn-in.
+ */
+public class ClockLayout extends FrameLayout {
+
+ /**
+ * Clock face views.
+ */
+ private View mDigitalClock;
+ private View mAnalogClock;
+
+ /**
+ * Pixel shifting amplitidues used to prevent screen burn-in.
+ */
+ private int mBurnInPreventionOffsetX;
+ private int mBurnInPreventionOffsetY;
+
+ public ClockLayout(Context context) {
+ this(context, null);
+ }
+
+ public ClockLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ClockLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDigitalClock = findViewById(R.id.digital_clock);
+ mAnalogClock = findViewById(R.id.analog_clock);
+
+ // Get pixel shifting X, Y amplitudes from resources.
+ Resources resources = getResources();
+ mBurnInPreventionOffsetX = resources.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_x);
+ mBurnInPreventionOffsetY = resources.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_y);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ final float offsetX = getBurnInOffset(mBurnInPreventionOffsetX, true);
+ final float offsetY = getBurnInOffset(mBurnInPreventionOffsetY, false);
+
+ // Put digital clock in two left corner of the screen.
+ if (mDigitalClock != null) {
+ mDigitalClock.setX(0.1f * getWidth() + offsetX);
+ mDigitalClock.setY(0.1f * getHeight() + offsetY);
+ }
+
+ // Put the analog clock in the middle of the screen.
+ if (mAnalogClock != null) {
+ mAnalogClock.setX(Math.max(0f, 0.5f * (getWidth() - mAnalogClock.getWidth()))
+ + offsetX);
+ mAnalogClock.setY(Math.max(0f, 0.5f * (getHeight() - mAnalogClock.getHeight()))
+ + offsetY);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
new file mode 100644
index 0000000..3c3f475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 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.keyguard.clock;
+
+import android.view.View;
+
+/**
+ * Controls transition to dark state by cross fading between views.
+ */
+final class CrossFadeDarkController {
+
+ private final View mFadeInView;
+ private final View mFadeOutView;
+
+ /**
+ * Creates a new controller that fades between views.
+ *
+ * @param fadeInView View to fade in when transitioning to AOD.
+ * @param fadeOutView View to fade out when transitioning to AOD.
+ */
+ CrossFadeDarkController(View fadeInView, View fadeOutView) {
+ mFadeInView = fadeInView;
+ mFadeOutView = fadeOutView;
+ }
+
+ /**
+ * Sets the amount the system has transitioned to the dark state.
+ *
+ * @param darkAmount Amount of transition to dark state: 1f for AOD and 0f for lock screen.
+ */
+ void setDarkAmount(float darkAmount) {
+ mFadeInView.setAlpha(Math.max(0f, 2f * darkAmount - 1f));
+ if (darkAmount == 0f) {
+ mFadeInView.setVisibility(View.GONE);
+ } else {
+ if (mFadeInView.getVisibility() == View.GONE) {
+ mFadeInView.setVisibility(View.VISIBLE);
+ }
+ }
+ mFadeOutView.setAlpha(Math.max(0f, 1f - 2f * darkAmount));
+ if (darkAmount == 1f) {
+ mFadeOutView.setVisibility(View.GONE);
+ } else {
+ if (mFadeOutView.getVisibility() == View.GONE) {
+ mFadeOutView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
new file mode 100644
index 0000000..2c709e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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.keyguard.clock;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.keyguard.R;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Clock composed of two images that rotate with the time.
+ *
+ * The images are the clock hands. ImageClock expects two child ImageViews
+ * with ids hour_hand and minute_hand.
+ */
+public class ImageClock extends FrameLayout {
+
+ private ImageView mHourHand;
+ private ImageView mMinuteHand;
+ private Calendar mTime;
+ private String mDescFormat;
+ private TimeZone mTimeZone;
+
+ public ImageClock(Context context) {
+ this(context, null);
+ }
+
+ public ImageClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ImageClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mTime = Calendar.getInstance();
+ mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
+ }
+
+ /**
+ * Call when the time changes to update the rotation of the clock hands.
+ */
+ public void onTimeChanged() {
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ final float hourAngle = mTime.get(Calendar.HOUR) * 30f;
+ mHourHand.setRotation(hourAngle);
+ final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
+ mMinuteHand.setRotation(minuteAngle);
+ setContentDescription(DateFormat.format(mDescFormat, mTime));
+ invalidate();
+ }
+
+ /**
+ * Call when the time zone has changed to update clock hands.
+ *
+ * @param timeZone The updated time zone that will be used.
+ */
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mTimeZone = timeZone;
+ mTime.setTimeZone(timeZone);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHourHand = findViewById(R.id.hour_hand);
+ mMinuteHand = findViewById(R.id.minute_hand);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
+ onTimeChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index d7bf77d..957d772 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -284,8 +284,9 @@
@Nullable
private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
Notification notification = notif.notification.getNotification();
- if (canLaunchInActivityView(notification.getAppOverlayIntent())) {
- return notification.getAppOverlayIntent();
+ if (canLaunchInActivityView(notification.getBubbleMetadata() != null
+ ? notification.getBubbleMetadata().getIntent() : null)) {
+ return notification.getBubbleMetadata().getIntent();
} else if (shouldUseContentIntent(mContext)
&& canLaunchInActivityView(notification.contentIntent)) {
Log.d(TAG, "[addBubble " + notif.key
@@ -446,15 +447,16 @@
StatusBarNotification n = entry.notification;
boolean canAppOverlay = false;
try {
- canAppOverlay = mNotificationManagerService.areAppOverlaysAllowedForPackage(
+ canAppOverlay = mNotificationManagerService.areBubblesAllowedForPackage(
n.getPackageName(), n.getUid());
} catch (RemoteException e) {
Log.w(TAG, "Error calling NoMan to determine if app can overlay", e);
}
boolean canChannelOverlay = mNotificationEntryManager.getNotificationData().getChannel(
- entry.key).canOverlayApps();
- boolean hasOverlayIntent = n.getNotification().getAppOverlayIntent() != null;
+ entry.key).canBubble();
+ boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null
+ && n.getNotification().getBubbleMetadata().getIntent() != null;
return hasOverlayIntent && canChannelOverlay && canAppOverlay;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 9422101..f79ad71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -541,7 +541,8 @@
@VisibleForTesting
void doUpdateMobileControllers() {
- List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+ List<SubscriptionInfo> subscriptions = mSubscriptionManager
+ .getActiveSubscriptionInfoList(true);
if (subscriptions == null) {
subscriptions = Collections.emptyList();
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
new file mode 100644
index 0000000..9e946fa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class BubbleClockControllerTest extends SysuiTestCase {
+
+ private BubbleClockController mClockController;
+
+ @Before
+ public void setUp() {
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ mClockController = BubbleClockController.build(layoutInflater);
+ }
+
+ @Test
+ public void setDarkAmount_fadeIn() {
+ ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
+ View smallClock = smallClockFrame.getChildAt(0);
+ // WHEN dark amount is set to AOD
+ mClockController.setDarkAmount(1f);
+ // THEN small clock should not be shown.
+ assertThat(smallClock.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setTextColor_setDigitalClock() {
+ ViewGroup smallClock = (ViewGroup) mClockController.getView();
+ // WHEN text color is set
+ mClockController.setTextColor(42);
+ // THEN child of small clock should have text color set.
+ TextView digitalClock = (TextView) smallClock.getChildAt(0);
+ assertThat(digitalClock.getCurrentTextColor()).isEqualTo(42);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
new file mode 100644
index 0000000..fd7657f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class CrossFadeDarkControllerTest extends SysuiTestCase {
+
+ private View mViewFadeIn;
+ private View mViewFadeOut;
+ private CrossFadeDarkController mDarkController;
+
+ @Before
+ public void setUp() {
+ mViewFadeIn = new TextView(getContext());
+ mViewFadeIn.setVisibility(View.VISIBLE);
+ mViewFadeIn.setAlpha(1f);
+ mViewFadeOut = new TextView(getContext());
+ mViewFadeOut.setVisibility(View.VISIBLE);
+ mViewFadeOut.setAlpha(1f);
+
+ mDarkController = new CrossFadeDarkController(mViewFadeIn, mViewFadeOut);
+ }
+
+ @Test
+ public void setDarkAmount_fadeIn() {
+ // WHEN dark amount corresponds to AOD
+ mDarkController.setDarkAmount(1f);
+ // THEN fade in view should be faded in and fade out view faded out.
+ assertThat(mViewFadeIn.getAlpha()).isEqualTo(1f);
+ assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewFadeOut.getAlpha()).isEqualTo(0f);
+ assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setDarkAmount_fadeOut() {
+ // WHEN dark amount corresponds to lock screen
+ mDarkController.setDarkAmount(0f);
+ // THEN fade out view should bed faded out and fade in view faded in.
+ assertThat(mViewFadeIn.getAlpha()).isEqualTo(0f);
+ assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewFadeOut.getAlpha()).isEqualTo(1f);
+ assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void setDarkAmount_partialFadeIn() {
+ // WHEN dark amount corresponds to a partial transition
+ mDarkController.setDarkAmount(0.9f);
+ // THEN views should have intermediate alpha value.
+ assertThat(mViewFadeIn.getAlpha()).isGreaterThan(0f);
+ assertThat(mViewFadeIn.getAlpha()).isLessThan(1f);
+ assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void setDarkAmount_partialFadeOut() {
+ // WHEN dark amount corresponds to a partial transition
+ mDarkController.setDarkAmount(0.1f);
+ // THEN views should have intermediate alpha value.
+ assertThat(mViewFadeOut.getAlpha()).isGreaterThan(0f);
+ assertThat(mViewFadeOut.getAlpha()).isLessThan(1f);
+ assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java
index 4583770..3415c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java
@@ -91,7 +91,7 @@
mMockPm = mock(PackageManager.class);
mMockListener = mock(PluginListener.class);
mMockManager = mock(PluginManagerImpl.class);
- when(mMockManager.getClassLoader(any(), any())).thenReturn(getClass().getClassLoader());
+ when(mMockManager.getClassLoader(any())).thenReturn(getClass().getClassLoader());
mMockEnabler = mock(PluginEnabler.class);
when(mMockManager.getPluginEnabler()).thenReturn(mMockEnabler);
mMockVersionInfo = mock(VersionInfo.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
index 76e68f1..536c043 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
@@ -25,6 +25,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;
@@ -135,9 +136,13 @@
});
resetExceptionHandler();
+ String sourceDir = "myPlugin";
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.sourceDir = sourceDir;
+ applicationInfo.packageName = WHITELISTED_PACKAGE;
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
- assertNull(mPluginManager.getOneShotPlugin("myPlugin", TestPlugin.class));
- assertNull(mPluginManager.getClassLoader("myPlugin", WHITELISTED_PACKAGE));
+ assertNull(mPluginManager.getOneShotPlugin(sourceDir, TestPlugin.class));
+ assertNull(mPluginManager.getClassLoader(applicationInfo));
}
@Test
@@ -152,9 +157,16 @@
});
resetExceptionHandler();
+ String sourceDir = "myPlugin";
+ ApplicationInfo whiteListedApplicationInfo = new ApplicationInfo();
+ whiteListedApplicationInfo.sourceDir = sourceDir;
+ whiteListedApplicationInfo.packageName = WHITELISTED_PACKAGE;
+ ApplicationInfo invalidApplicationInfo = new ApplicationInfo();
+ invalidApplicationInfo.sourceDir = sourceDir;
+ invalidApplicationInfo.packageName = "com.android.invalidpackage";
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
- assertNotNull(mPluginManager.getClassLoader("myPlugin", WHITELISTED_PACKAGE));
- assertNull(mPluginManager.getClassLoader("myPlugin", "com.android.invalidpackage"));
+ assertNotNull(mPluginManager.getClassLoader(whiteListedApplicationInfo));
+ assertNull(mPluginManager.getClassLoader(invalidApplicationInfo));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 2b13f86..8952594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -27,6 +27,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
@@ -220,8 +221,7 @@
notificationBuilder.setGroup(groupKey);
}
if (isBubble) {
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- notificationBuilder.setAppOverlayIntent(bubbleIntent);
+ notificationBuilder.setBubbleMetadata(makeBubbleMetadata());
}
return notificationBuilder.build();
}
@@ -282,4 +282,14 @@
mGroupManager.onEntryAdded(entry);
return row;
}
+
+ private Notification.BubbleMetadata makeBubbleMetadata() {
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ return new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setTitle("bubble title")
+ .setIcon(Icon.createWithResource(mContext, 1))
+ .setDesiredHeight(314)
+ .build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index fdbf090..c1f8885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -182,6 +182,7 @@
subs.add(subscription);
}
when(mMockSm.getActiveSubscriptionInfoList()).thenReturn(subs);
+ when(mMockSm.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subs);
mNetworkController.doUpdateMobileControllers();
}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 44edb56..bb5d288 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6803,6 +6803,15 @@
ACTION_NFC_PAYMENT_ALWAYS_SETTING = 1623;
// ---- End Q Constants, all Q constants go above this line ----
+ // OPEN: Settings > System > Input & Gesture > Skip song gesture
+ // OS: Q
+ SETTINGS_GESTURE_SKIP_SONG = 1624;
+
+ // OPEN: Settings > System > Input & Gesture > Silence gesture
+ // OS: Q
+ SETTINGS_GESTURE_SILENCE = 1625;
+
+ // ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 24fd7b9..ec6d20d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -701,6 +701,11 @@
public void onBackKeyPressed() {
if (sDebug) Slog.d(TAG, "onBackKeyPressed()");
mUi.hideAll(null);
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ getServiceForUserLocked(UserHandle.getCallingUserId());
+ service.onBackKeyPressed();
+ }
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index a6bb049..d037b08 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -187,6 +187,15 @@
}
@GuardedBy("mLock")
+ void onBackKeyPressed() {
+ final RemoteAugmentedAutofillService remoteService =
+ getRemoteAugmentedAutofillServiceLocked();
+ if (remoteService != null) {
+ remoteService.onDestroyAutofillWindowsRequest();
+ }
+ }
+
+ @GuardedBy("mLock")
@Override // from PerUserSystemService
protected boolean updateLocked(boolean disabled) {
destroySessionsLocked();
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index a8ff9b0..5d8d8fa 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -109,8 +109,8 @@
/**
* Called by {@link Session} when it's time to destroy all augmented autofill requests.
*/
- public void onDestroyAutofillWindowsRequest(int sessionId) {
- scheduleAsyncRequest((s) -> s.onDestroyFillWindowRequest(sessionId));
+ public void onDestroyAutofillWindowsRequest() {
+ scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
}
// TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index cf4963c..a5ef21a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2619,7 +2619,7 @@
currentValue);
if (mAugmentedAutofillDestroyer == null) {
- mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(id);
+ mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
}
return mAugmentedAutofillDestroyer;
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 09aa421..01778cd 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -17,6 +17,9 @@
package com.android.server.contentcapture;
import static android.service.contentcapture.ContentCaptureService.setClientState;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID;
+import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
@@ -39,7 +42,6 @@
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
import android.util.Slog;
-import android.view.contentcapture.ContentCaptureSession;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
@@ -165,7 +167,7 @@
if (!isEnabledLocked()) {
// TODO: it would be better to split in differet reasons, like
// STATE_DISABLED_NO_SERVICE and STATE_DISABLED_BY_DEVICE_POLICY
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+ setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
/* binder= */ null);
return;
}
@@ -184,7 +186,7 @@
if (existingSession != null) {
Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
+ ": ignoring because it already exists for " + existingSession.mActivityToken);
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_DUPLICATED_ID,
+ setClientState(clientReceiver, STATE_DISABLED | STATE_DUPLICATED_ID,
/* binder=*/ null);
return;
}
@@ -197,8 +199,7 @@
// TODO(b/119613670): log metrics
Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
+ ": ignoring because service is not set");
- // TODO(b/111276913): use a new disabled state?
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+ setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
/* binder= */ null);
return;
}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 869d564..312b83c 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1041,12 +1041,13 @@
@Override
public void onSetProperties(ProviderProperties properties) {
- // move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
- synchronized (mLock) {
- mProperties = properties;
- }
- });
+ // because this does not invoke any other methods which might result in calling back
+ // into the location provider, it is safe to run this on the calling thread. it is also
+ // currently necessary to run this on the calling thread to ensure that property changes
+ // are publicly visibly immediately, ie for mock providers which are created.
+ synchronized (mLock) {
+ mProperties = properties;
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd6fa49..d292783 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -32,6 +32,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
import static android.content.pm.PackageManager.GET_PROVIDERS;
+import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -318,7 +319,6 @@
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.AlarmManagerInternal;
-import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleController;
import com.android.server.DisplayThread;
@@ -337,6 +337,7 @@
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.appop.AppOpsService;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.pm.Installer;
@@ -658,9 +659,47 @@
/**
* When an app has restrictions on the other apps that can have associations with it,
- * it appears here with a set of the allowed apps.
+ * it appears here with a set of the allowed apps and also track debuggability of the app.
*/
- ArrayMap<String, ArraySet<String>> mAllowedAssociations;
+ ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
+
+ /**
+ * Tracks association information for a particular package along with debuggability.
+ * <p> Associations for a package A are allowed to package B if B is part of the
+ * allowed associations for A or if A is debuggable.
+ */
+ private final class PackageAssociationInfo {
+ private final String mSourcePackage;
+ private final ArraySet<String> mAllowedPackageAssociations;
+ private boolean mIsDebuggable;
+
+ PackageAssociationInfo(String sourcePackage, ArraySet<String> allowedPackages,
+ boolean isDebuggable) {
+ mSourcePackage = sourcePackage;
+ mAllowedPackageAssociations = allowedPackages;
+ mIsDebuggable = isDebuggable;
+ }
+
+ /**
+ * Returns true if {@code mSourcePackage} is allowed association with
+ * {@code targetPackage}.
+ */
+ boolean isPackageAssociationAllowed(String targetPackage) {
+ return mIsDebuggable || mAllowedPackageAssociations.contains(targetPackage);
+ }
+
+ boolean isDebuggable() {
+ return mIsDebuggable;
+ }
+
+ void setDebuggable(boolean isDebuggable) {
+ mIsDebuggable = isDebuggable;
+ }
+
+ ArraySet<String> getAllowedPackageAssociations() {
+ return mAllowedPackageAssociations;
+ }
+ }
/**
* All of the processes we currently have running organized by pid.
@@ -2392,12 +2431,10 @@
* If it does not, give it an empty set.
*/
void requireAllowedAssociationsLocked(String packageName) {
- if (mAllowedAssociations == null) {
- mAllowedAssociations = new ArrayMap<>(
- SystemConfig.getInstance().getAllowedAssociations());
- }
+ ensureAllowedAssociations();
if (mAllowedAssociations.get(packageName) == null) {
- mAllowedAssociations.put(packageName, new ArraySet<>());
+ mAllowedAssociations.put(packageName, new PackageAssociationInfo(packageName,
+ new ArraySet<>(), /* isDebuggable = */ false));
}
}
@@ -2408,10 +2445,7 @@
* association is implicitly allowed.
*/
boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
- if (mAllowedAssociations == null) {
- mAllowedAssociations = new ArrayMap<>(
- SystemConfig.getInstance().getAllowedAssociations());
- }
+ ensureAllowedAssociations();
// Interactions with the system uid are always allowed, since that is the core system
// that everyone needs to be able to interact with. Also allow reflexive associations
// within the same uid.
@@ -2419,24 +2453,57 @@
|| UserHandle.getAppId(uid2) == SYSTEM_UID) {
return true;
}
- // We won't allow this association if either pkg1 or pkg2 has a limit on the
- // associations that are allowed with it, and the other package is not explicitly
- // specified as one of those associations.
- ArraySet<String> pkgs = mAllowedAssociations.get(pkg1);
- if (pkgs != null) {
- if (!pkgs.contains(pkg2)) {
- return false;
- }
+
+ // Check for association on both source and target packages.
+ PackageAssociationInfo pai = mAllowedAssociations.get(pkg1);
+ if (pai != null && !pai.isPackageAssociationAllowed(pkg2)) {
+ return false;
}
- pkgs = mAllowedAssociations.get(pkg2);
- if (pkgs != null) {
- return pkgs.contains(pkg1);
+ pai = mAllowedAssociations.get(pkg2);
+ if (pai != null && !pai.isPackageAssociationAllowed(pkg1)) {
+ return false;
}
// If no explicit associations are provided in the manifest, then assume the app is
// allowed associations with any package.
return true;
}
+ /** Sets up allowed associations for system prebuilt packages from system config (if needed). */
+ private void ensureAllowedAssociations() {
+ if (mAllowedAssociations == null) {
+ ArrayMap<String, ArraySet<String>> allowedAssociations =
+ SystemConfig.getInstance().getAllowedAssociations();
+ mAllowedAssociations = new ArrayMap<>(allowedAssociations.size());
+ PackageManagerInternal pm = getPackageManagerInternalLocked();
+ for (int i = 0; i < allowedAssociations.size(); i++) {
+ final String pkg = allowedAssociations.keyAt(i);
+ final ArraySet<String> asc = allowedAssociations.valueAt(i);
+
+ // Query latest debuggable flag from package-manager.
+ boolean isDebuggable = false;
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager()
+ .getApplicationInfo(pkg, MATCH_ALL, 0);
+ if (ai != null) {
+ isDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ }
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ mAllowedAssociations.put(pkg, new PackageAssociationInfo(pkg, asc, isDebuggable));
+ }
+ }
+ }
+
+ /** Updates allowed associations for app info (specifically, based on debuggability). */
+ private void updateAssociationForApp(ApplicationInfo appInfo) {
+ ensureAllowedAssociations();
+ PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
+ if (pai != null) {
+ pai.setDebuggable((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+ }
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -10910,14 +10977,14 @@
void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
- boolean printedAnything = false;
pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
boolean printed = false;
if (mAllowedAssociations != null) {
for (int i = 0; i < mAllowedAssociations.size(); i++) {
final String pkg = mAllowedAssociations.keyAt(i);
- final ArraySet<String> asc = mAllowedAssociations.valueAt(i);
+ final ArraySet<String> asc =
+ mAllowedAssociations.valueAt(i).getAllowedPackageAssociations();
boolean printedHeader = false;
for (int j = 0; j < asc.size(); j++) {
if (dumpPackage == null || pkg.equals(dumpPackage)
@@ -10926,7 +10993,6 @@
pw.println(" Allowed associations (by restricted package):");
printed = true;
needSep = true;
- printedAnything = true;
}
if (!printedHeader) {
pw.print(" * ");
@@ -10938,6 +11004,9 @@
pw.println(asc.valueAt(j));
}
}
+ if (mAllowedAssociations.valueAt(i).isDebuggable()) {
+ pw.println(" (debuggable)");
+ }
}
}
if (!printed) {
@@ -14519,6 +14588,7 @@
+ " ssp=" + ssp + " data=" + data);
return ActivityManager.BROADCAST_SUCCESS;
}
+ updateAssociationForApp(aInfo);
mAtmInternal.onPackageReplaced(aInfo);
mServices.updateServiceApplicationInfoLocked(aInfo);
sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d704a3e..9a4ca1f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7824,8 +7824,10 @@
/** see AudioPolicy.setUidDeviceAffinity() */
public int setUidDeviceAffinity(IAudioPolicyCallback pcb, int uid,
- @NonNull int[] deviceTypes,
- @NonNull String[] deviceAddresses) {
+ @NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) {
+ if (DEBUG_AP) {
+ Log.d(TAG, "setUidDeviceAffinity for " + pcb.asBinder() + " uid:" + uid);
+ }
synchronized (mAudioPolicies) {
final AudioPolicyProxy app =
checkUpdateForPolicy(pcb, "Cannot change device affinity in audio policy");
@@ -7835,21 +7837,23 @@
if (!app.hasMixRoutedToDevices(deviceTypes, deviceAddresses)) {
return AudioManager.ERROR;
}
+ return app.setUidDeviceAffinities(uid, deviceTypes, deviceAddresses);
}
- return AudioManager.SUCCESS;
}
/** see AudioPolicy.removeUidDeviceAffinity() */
public int removeUidDeviceAffinity(IAudioPolicyCallback pcb, int uid) {
+ if (DEBUG_AP) {
+ Log.d(TAG, "removeUidDeviceAffinity for " + pcb.asBinder() + " uid:" + uid);
+ }
synchronized (mAudioPolicies) {
final AudioPolicyProxy app =
checkUpdateForPolicy(pcb, "Cannot remove device affinity in audio policy");
if (app == null) {
return AudioManager.ERROR;
}
-
+ return app.removeUidDeviceAffinities(uid);
}
- return AudioManager.SUCCESS;
}
public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) {
@@ -8160,27 +8164,41 @@
Binder.restoreCallingIdentity(identity);
}
- void setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
+ int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
final Integer Uid = new Integer(uid);
+ int res;
if (mUidDeviceAffinities.remove(Uid) != null) {
final long identity = Binder.clearCallingIdentity();
- AudioSystem.removeUidDeviceAffinities(uid);
+ res = AudioSystem.removeUidDeviceAffinities(uid);
Binder.restoreCallingIdentity(identity);
+ if (res != AudioSystem.SUCCESS) {
+ Log.e(TAG, "AudioSystem. removeUidDeviceAffinities(" + uid + ") failed, "
+ + " cannot call AudioSystem.setUidDeviceAffinities");
+ return AudioManager.ERROR;
+ }
}
final long identity = Binder.clearCallingIdentity();
- final int res = AudioSystem.setUidDeviceAffinities(uid, types, addresses);
+ res = AudioSystem.setUidDeviceAffinities(uid, types, addresses);
Binder.restoreCallingIdentity(identity);
if (res == AudioSystem.SUCCESS) {
mUidDeviceAffinities.put(Uid, new AudioDeviceArray(types, addresses));
+ return AudioManager.SUCCESS;
}
+ Log.e(TAG, "AudioSystem. setUidDeviceAffinities(" + uid + ") failed");
+ return AudioManager.ERROR;
}
- void removeUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
+ int removeUidDeviceAffinities(int uid) {
if (mUidDeviceAffinities.remove(new Integer(uid)) != null) {
final long identity = Binder.clearCallingIdentity();
- AudioSystem.removeUidDeviceAffinities(uid);
+ final int res = AudioSystem.removeUidDeviceAffinities(uid);
Binder.restoreCallingIdentity(identity);
+ if (res == AudioSystem.SUCCESS) {
+ return AudioManager.SUCCESS;
+ }
}
+ Log.e(TAG, "AudioSystem. removeUidDeviceAffinities failed");
+ return AudioManager.ERROR;
}
};
diff --git a/services/core/java/com/android/server/content/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java
index 5cbe5b9..fc20ef20 100644
--- a/services/core/java/com/android/server/content/SyncLogger.java
+++ b/services/core/java/com/android/server/content/SyncLogger.java
@@ -16,6 +16,7 @@
package com.android.server.content;
+import android.accounts.Account;
import android.app.job.JobParameters;
import android.os.Build;
import android.os.Environment;
@@ -31,6 +32,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IntPair;
import com.android.server.IoThread;
+import com.android.server.content.SyncManager.ActiveSyncContext;
+import com.android.server.content.SyncStorageEngine.EndPoint;
import libcore.io.IoUtils;
@@ -309,4 +312,20 @@
}
}
}
+
+ static String logSafe(Account account) {
+ return account == null ? "[null]" : account.toSafeString();
+ }
+
+ static String logSafe(EndPoint endPoint) {
+ return endPoint == null ? "[null]" : endPoint.toSafeString();
+ }
+
+ static String logSafe(SyncOperation operation) {
+ return operation == null ? "[null]" : operation.toSafeString();
+ }
+
+ static String logSafe(ActiveSyncContext asc) {
+ return asc == null ? "[null]" : asc.toSafeString();
+ }
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 8b93e04..7096477 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -16,6 +16,8 @@
package com.android.server.content;
+import static com.android.server.content.SyncLogger.logSafe;
+
import android.accounts.Account;
import android.accounts.AccountAndUser;
import android.accounts.AccountManager;
@@ -1140,7 +1142,7 @@
/* ignore - local call */
}
if (checkAccountAccess && !canAccessAccount(account, owningPackage, owningUid)) {
- Log.w(TAG, "Access to " + account + " denied for package "
+ Log.w(TAG, "Access to " + logSafe(account) + " denied for package "
+ owningPackage + " in UID " + syncAdapterInfo.uid);
return AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS;
}
@@ -1474,7 +1476,8 @@
if (!syncOperation.ignoreBackoff()) {
Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(syncOperation.target);
if (backoff == null) {
- Slog.e(TAG, "Couldn't find backoff values for " + syncOperation.target);
+ Slog.e(TAG, "Couldn't find backoff values for "
+ + logSafe(syncOperation.target));
backoff = new Pair<Long, Long>(SyncStorageEngine.NOT_IN_BACKOFF_MODE,
SyncStorageEngine.NOT_IN_BACKOFF_MODE);
}
@@ -1745,8 +1748,8 @@
scheduleSyncOperationH(operation);
} else {
// Otherwise do not reschedule.
- Log.d(TAG, "not retrying sync operation because the error is a hard error: "
- + operation);
+ Log.e(TAG, "not retrying sync operation because the error is a hard error: "
+ + logSafe(operation));
}
}
@@ -1881,11 +1884,12 @@
sendSyncFinishedOrCanceledMessage(this, result);
}
- public void toString(StringBuilder sb) {
+ public void toString(StringBuilder sb, boolean logSafe) {
sb.append("startTime ").append(mStartTime)
.append(", mTimeoutStartTime ").append(mTimeoutStartTime)
.append(", mHistoryRowId ").append(mHistoryRowId)
- .append(", syncOperation ").append(mSyncOperation);
+ .append(", syncOperation ").append(
+ logSafe ? logSafe(mSyncOperation) : mSyncOperation);
}
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -1947,7 +1951,13 @@
public String toString() {
StringBuilder sb = new StringBuilder();
- toString(sb);
+ toString(sb, false);
+ return sb.toString();
+ }
+
+ public String toSafeString() {
+ StringBuilder sb = new StringBuilder();
+ toString(sb, true);
return sb.toString();
}
@@ -2036,7 +2046,7 @@
int count = 0;
for (SyncOperation op: pendingSyncs) {
if (!op.isPeriodic) {
- pw.println(op.dump(null, false, buckets));
+ pw.println(op.dump(null, false, buckets, /*logSafe=*/ false));
count++;
}
}
@@ -2053,7 +2063,7 @@
int count = 0;
for (SyncOperation op: pendingSyncs) {
if (op.isPeriodic) {
- pw.println(op.dump(null, false, buckets));
+ pw.println(op.dump(null, false, buckets, /*logSafe=*/ false));
count++;
}
}
@@ -2186,7 +2196,7 @@
sb.setLength(0);
pw.print(formatDurationHMS(sb, durationInSeconds));
pw.print(" - ");
- pw.print(activeSyncContext.mSyncOperation.dump(pm, false, buckets));
+ pw.print(activeSyncContext.mSyncOperation.dump(pm, false, buckets, /*logSafe=*/ false));
pw.println();
}
pw.println();
@@ -2974,7 +2984,7 @@
case SyncHandler.MESSAGE_CANCEL:
SyncStorageEngine.EndPoint endpoint = (SyncStorageEngine.EndPoint) msg.obj;
Bundle extras = msg.peekData();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (isLoggable) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_CANCEL: "
+ endpoint + " bundle: " + extras);
}
@@ -2985,9 +2995,11 @@
SyncFinishedOrCancelledMessagePayload payload =
(SyncFinishedOrCancelledMessagePayload) msg.obj;
if (!isSyncStillActiveH(payload.activeSyncContext)) {
- Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
- + "sync is no longer active: "
- + payload.activeSyncContext);
+ if (isLoggable) {
+ Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+ + "sync is no longer active: "
+ + payload.activeSyncContext);
+ }
break;
}
if (isLoggable) {
@@ -3002,7 +3014,7 @@
case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
ServiceConnectionData msgData = (ServiceConnectionData) msg.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (isLoggable) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
+ msgData.activeSyncContext);
}
@@ -3018,7 +3030,7 @@
case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
final ActiveSyncContext currentSyncContext =
((ServiceConnectionData) msg.obj).activeSyncContext;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (isLoggable) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
+ currentSyncContext);
}
@@ -3053,7 +3065,7 @@
case SyncHandler.MESSAGE_MONITOR_SYNC:
ActiveSyncContext monitoredSyncContext = (ActiveSyncContext) msg.obj;
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (isLoggable) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_MONITOR_SYNC: " +
monitoredSyncContext.mSyncOperation.target);
}
@@ -3061,7 +3073,7 @@
if (isSyncNotUsingNetworkH(monitoredSyncContext)) {
Log.w(TAG, String.format(
"Detected sync making no progress for %s. cancelling.",
- monitoredSyncContext));
+ logSafe(monitoredSyncContext)));
SyncJobService.callJobFinished(
monitoredSyncContext.mSyncOperation.jobId, false,
"no network activity");
@@ -3558,7 +3570,8 @@
} catch (RuntimeException exc) {
mLogger.log("Sync failed with RuntimeException: ", exc.toString());
closeActiveSyncContext(activeSyncContext);
- Slog.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
+ Slog.e(TAG, "Caught RuntimeException while starting the sync "
+ + logSafe(syncOperation), exc);
}
}
@@ -3658,7 +3671,8 @@
reschedulePeriodicSyncH(syncOperation);
}
} else {
- Log.w(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+ Log.w(TAG, "failed sync operation "
+ + logSafe(syncOperation) + ", " + syncResult);
syncOperation.retries++;
if (syncOperation.retries > mConstants.getMaxRetriesWithAppStandbyExemption()) {
@@ -4042,11 +4056,6 @@
getJobScheduler().cancel(op.jobId);
}
- private void wtfWithLog(String message) {
- Slog.wtf(TAG, message);
- mLogger.log("WTF: ", message);
- }
-
public void resetTodayStats() {
mSyncStorageEngine.resetTodayStats(/*force=*/ true);
}
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 25edf40..2abc2e6 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -363,14 +363,19 @@
@Override
public String toString() {
- return dump(null, true, null);
+ return dump(null, true, null, false);
}
- String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates) {
+ public String toSafeString() {
+ return dump(null, true, null, true);
+ }
+
+ String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates,
+ boolean logSafe) {
StringBuilder sb = new StringBuilder();
sb.append("JobId=").append(jobId)
.append(" ")
- .append(target.account.name)
+ .append(logSafe ? "***" : target.account.name)
.append("/")
.append(target.account.type)
.append(" u")
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index bfd1791..6b441a0 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -16,6 +16,8 @@
package com.android.server.content;
+import static com.android.server.content.SyncLogger.logSafe;
+
import android.accounts.Account;
import android.accounts.AccountAndUser;
import android.accounts.AccountManager;
@@ -225,6 +227,15 @@
sb.append(":u" + userId);
return sb.toString();
}
+
+ public String toSafeString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(account == null ? "ALL ACCS" : logSafe(account))
+ .append("/")
+ .append(provider == null ? "ALL PDRS" : provider);
+ sb.append(":u" + userId);
+ return sb.toString();
+ }
}
public static class AuthorityInfo {
@@ -1861,8 +1872,8 @@
}
} else {
- Slog.w(TAG, "Failure adding authority: account="
- + accountName + " auth=" + authorityName
+ Slog.w(TAG, "Failure adding authority:"
+ + " auth=" + authorityName
+ " enabled=" + enabled
+ " syncable=" + syncable);
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2340b77..cb3f91b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -792,6 +792,16 @@
}
}
+ private void setVirtualDisplayStateInternal(IBinder appToken, boolean isOn) {
+ synchronized (mSyncRoot) {
+ if (mVirtualDisplayAdapter == null) {
+ return;
+ }
+
+ mVirtualDisplayAdapter.setVirtualDisplayStateLocked(appToken, isOn);
+ }
+ }
+
private void registerDefaultDisplayAdapters() {
// Register default display adapters.
synchronized (mSyncRoot) {
@@ -1930,6 +1940,16 @@
}
@Override // Binder call
+ public void setVirtualDisplayState(IVirtualDisplayCallback callback, boolean isOn) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setVirtualDisplayStateInternal(callback.asBinder(), isOn);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 5aa585f..1ca8dd3 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -147,6 +147,13 @@
return device;
}
+ void setVirtualDisplayStateLocked(IBinder appToken, boolean isOn) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+ if (device != null) {
+ device.setDisplayState(isOn);
+ }
+ }
+
/**
* Returns the next unique index for the uniqueIdPrefix
*/
@@ -206,6 +213,7 @@
private int mPendingChanges;
private int mUniqueIndex;
private Display.Mode mMode;
+ private boolean mIsDisplayOn;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName,
@@ -226,6 +234,7 @@
mDisplayState = Display.STATE_UNKNOWN;
mPendingChanges |= PENDING_SURFACE_CHANGE;
mUniqueIndex = uniqueIndex;
+ mIsDisplayOn = surface != null;
}
@Override
@@ -304,6 +313,14 @@
}
}
+ void setDisplayState(boolean isOn) {
+ if (mIsDisplayOn != isOn) {
+ mIsDisplayOn = isOn;
+ mInfo = null;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+ }
+ }
+
public void stopLocked() {
setSurfaceLocked(null);
mStopped = true;
@@ -375,7 +392,9 @@
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL;
- mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
+
+ mInfo.state = mIsDisplayOn ? Display.STATE_ON : Display.STATE_OFF;
+
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index ba21b78..af71624 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -51,6 +51,8 @@
private static final int STATE_WAITING_FOR_OSD_NAME = 3;
// State in which the action is waiting for gathering vendor id of non-local devices.
private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
+ // State in which the action is waiting for devices to be ready
+ private static final int STATE_WAITING_FOR_DEVICES = 5;
/**
* Interface used to report result of device discovery.
@@ -89,7 +91,20 @@
private final DeviceDiscoveryCallback mCallback;
private int mProcessedDeviceCount = 0;
private int mTimeoutRetry = 0;
- private boolean mIsTvDevice = source().mService.isTvDevice();
+ private boolean mIsTvDevice = localDevice().mService.isTvDevice();
+ private final int mDelayPeriod;
+
+ /**
+ * Constructor.
+ *
+ * @param source an instance of {@link HdmiCecLocalDevice}.
+ * @param delay delay action for this period between query Physical Address and polling
+ */
+ DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback, int delay) {
+ super(source);
+ mCallback = Preconditions.checkNotNull(callback);
+ mDelayPeriod = delay;
+ }
/**
* Constructor.
@@ -97,8 +112,7 @@
* @param source an instance of {@link HdmiCecLocalDevice}.
*/
DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
- super(source);
- mCallback = Preconditions.checkNotNull(callback);
+ this(source, callback, 0);
}
@Override
@@ -117,7 +131,11 @@
Slog.v(TAG, "Device detected: " + ackedAddress);
allocateDevices(ackedAddress);
- startPhysicalAddressStage();
+ if (mDelayPeriod > 0) {
+ startToDelayAction();
+ } else {
+ startPhysicalAddressStage();
+ }
}
}, Constants.POLL_ITERATION_REVERSE_ORDER
| Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
@@ -131,6 +149,13 @@
}
}
+ private void startToDelayAction() {
+ Slog.v(TAG, "Waiting for connected devices to be ready");
+ mState = STATE_WAITING_FOR_DEVICES;
+
+ checkAndProceedStage();
+ }
+
private void startPhysicalAddressStage() {
Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
mProcessedDeviceCount = 0;
@@ -159,6 +184,11 @@
addTimer(mState, HdmiConfig.TIMEOUT_MS);
}
+ private void delayActionWithTimePeriod(int timeDelay) {
+ mActionTimer.clearTimerMessage();
+ addTimer(mState, timeDelay);
+ }
+
private void startOsdNameStage() {
Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
mProcessedDeviceCount = 0;
@@ -385,6 +415,9 @@
private void sendQueryCommand() {
int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
switch (mState) {
+ case STATE_WAITING_FOR_DEVICES:
+ delayActionWithTimePeriod(mDelayPeriod);
+ return;
case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
queryPhysicalAddress(address);
return;
@@ -405,6 +438,10 @@
return;
}
+ if (mState == STATE_WAITING_FOR_DEVICES) {
+ startPhysicalAddressStage();
+ return;
+ }
if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
sendQueryCommand();
return;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index e777ce8..86be585 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -77,7 +77,7 @@
private static final int NUM_LOGICAL_ADDRESS = 16;
- private static final int MAX_CEC_MESSAGE_HISTORY = 20;
+ private static final int MAX_CEC_MESSAGE_HISTORY = 200;
// Predicate for whether the given logical address is remote device's one or not.
private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
@@ -682,7 +682,7 @@
void dump(final IndentingPrintWriter pw) {
for (int i = 0; i < mLocalDevices.size(); ++i) {
- pw.println("HdmiCecLocalDevice #" + i + ":");
+ pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
pw.increaseIndent();
mLocalDevices.valueAt(i).dump(pw);
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 0e4e334..5c1b3de 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -26,22 +26,29 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.tv.TvContract;
import android.os.SystemProperties;
+import android.provider.Settings.Global;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.Constants.AudioCodec;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.stream.Collectors;
+
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
@@ -83,10 +90,10 @@
protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mSystemAudioControlFeatureEnabled = true;
- // TODO(amyjojo) make System Audio Control controllable by users
- /*mSystemAudioControlFeatureEnabled =
- mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/
+ mRoutingControlFeatureEnabled =
+ mService.readBooleanSetting(Global.HDMI_CEC_SWITCH_ENABLED, true);
+ mSystemAudioControlFeatureEnabled =
+ mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
// TODO(amyjojo): make the map ro property.
mTvInputs.put(Constants.CEC_SWITCH_HDMI1,
"com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
@@ -267,7 +274,7 @@
int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|| ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
- && lastSystemAudioControlStatus)) {
+ && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
}
}
@@ -400,8 +407,11 @@
@ServiceThreadOnly
protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
-
- reportAudioStatus(message);
+ if (isSystemAudioControlFeatureEnabled()) {
+ reportAudioStatus(message.getSource());
+ } else {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ }
return true;
}
@@ -478,14 +488,91 @@
private byte[] getSupportedShortAudioDescriptors(
AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) {
- // TODO(b/80297701) implement
- return new byte[] {};
+ ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
+ for (@AudioCodec int audioFormatCode : audioFormatCodes) {
+ byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode);
+ if (sad != null) {
+ if (sad.length == 3) {
+
+ sads.add(sad);
+ } else {
+ HdmiLogger.warning(
+ "Dropping Short Audio Descriptor with length %d for requested codec %x",
+ sad.length, audioFormatCode);
+ }
+ }
+ }
+ // Short Audio Descriptors are always 3 bytes long.
+ byte[] bytes = new byte[sads.size() * 3];
+ int index = 0;
+ for (byte[] sad : sads) {
+ System.arraycopy(sad, 0, bytes, index, 3);
+ index += 3;
+ }
+ return bytes;
+ }
+
+ /**
+ * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
+ * audioFormatCode is not supported.
+ */
+ @Nullable
+ private byte[] getSupportedShortAudioDescriptor(
+ AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) {
+ switch (audioFormatCode) {
+ case Constants.AUDIO_CODEC_NONE: {
+ return null;
+ }
+ case Constants.AUDIO_CODEC_LPCM: {
+ return getLpcmShortAudioDescriptor(deviceInfo);
+ }
+ // TODO(b/80297701): implement the rest of the codecs
+ case Constants.AUDIO_CODEC_DD:
+ case Constants.AUDIO_CODEC_MPEG1:
+ case Constants.AUDIO_CODEC_MP3:
+ case Constants.AUDIO_CODEC_MPEG2:
+ case Constants.AUDIO_CODEC_AAC:
+ case Constants.AUDIO_CODEC_DTS:
+ case Constants.AUDIO_CODEC_ATRAC:
+ case Constants.AUDIO_CODEC_ONEBITAUDIO:
+ case Constants.AUDIO_CODEC_DDP:
+ case Constants.AUDIO_CODEC_DTSHD:
+ case Constants.AUDIO_CODEC_TRUEHD:
+ case Constants.AUDIO_CODEC_DST:
+ case Constants.AUDIO_CODEC_WMAPRO:
+ default: {
+ return null;
+ }
+ }
+ }
+
+ @Nullable
+ private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) {
+ // TODO(b/80297701): implement
+ return null;
}
@Nullable
private AudioDeviceInfo getSystemAudioDeviceInfo() {
- // TODO(b/80297701) implement
- // Get the audio device used for system audio mode.
+ AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
+ if (audioManager == null) {
+ HdmiLogger.error(
+ "Error getting system audio device because AudioManager not available.");
+ return null;
+ }
+ AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ HdmiLogger.debug("Found %d audio input devices", devices.length);
+ for (AudioDeviceInfo device : devices) {
+ HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
+ HdmiLogger.debug("Supported encodings are %s",
+ Arrays.stream(device.getEncodings()).mapToObj(
+ AudioFormat::toLogFriendlyEncoding
+ ).collect(Collectors.joining(", ")));
+ // TODO(b/80297701) use the actual device type that system audio mode is connected to.
+ if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
+ return device;
+ }
+ }
return null;
}
@@ -583,17 +670,20 @@
.setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
}
- private void reportAudioStatus(HdmiCecMessage message) {
+ void reportAudioStatus(int source) {
assertRunOnServiceThread();
int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+ HdmiLogger.debug("Reporting volume %i (%i-%i) as CEC volume %i", volume,
+ minVolume, maxVolume, scaledVolume);
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportAudioStatus(
- mAddress, message.getSource(), scaledVolume, mute));
+ mAddress, source, scaledVolume, mute));
}
/**
@@ -688,6 +778,13 @@
HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
}
+ void onSystemAduioControlFeatureSupportChanged(boolean enabled) {
+ setSystemAudioControlFeatureEnabled(enabled);
+ if (enabled) {
+ addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
+ }
+ }
+
@ServiceThreadOnly
void setSystemAudioControlFeatureEnabled(boolean enabled) {
assertRunOnServiceThread();
@@ -697,6 +794,14 @@
}
@ServiceThreadOnly
+ void setRoutingControlFeatureEnables(boolean enabled) {
+ assertRunOnServiceThread();
+ synchronized (mLock) {
+ mRoutingControlFeatureEnabled = enabled;
+ }
+ }
+
+ @ServiceThreadOnly
void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
assertRunOnServiceThread();
// TODO: validate port ID
@@ -831,6 +936,10 @@
}
protected void routeToInputFromPortId(int portId) {
+ if (!isRoutingControlFeatureEnabled()) {
+ HdmiLogger.debug("Routing Control Feature is not enabled.");
+ return;
+ }
if (mArcIntentUsed) {
routeToTvInputFromPortId(portId);
} else {
@@ -978,4 +1087,20 @@
}
mDeviceInfos.clear();
}
+
+ @Override
+ protected void dump(IndentingPrintWriter pw) {
+ pw.println("HdmiCecLocalDeviceAudioSystem:");
+ pw.increaseIndent();
+ pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
+ pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
+ pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
+ pw.println("mArcEstablished: " + mArcEstablished);
+ pw.println("mArcIntentUsed: " + mArcIntentUsed);
+ HdmiUtils.dumpMap(pw, "mTvInputs:", mTvInputs);
+ HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
+ pw.decreaseIndent();
+ super.dump(pw);
+ }
+
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index a95f7f1..cbddaf5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -66,6 +66,10 @@
@LocalActivePort
protected int mLocalActivePort = Constants.CEC_SWITCH_HOME;
+ // Whether the Routing Coutrol feature is enabled or not. True by default.
+ @GuardedBy("mLock")
+ protected boolean mRoutingControlFeatureEnabled;
+
protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
super(service, deviceType);
}
@@ -123,7 +127,9 @@
}
setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
- switchInputOnReceivingNewActivePath(physicalAddress);
+ if (isRoutingControlFeatureEnabled()) {
+ switchInputOnReceivingNewActivePath(physicalAddress);
+ }
return true;
}
@@ -153,6 +159,10 @@
@ServiceThreadOnly
protected boolean handleRoutingChange(HdmiCecMessage message) {
assertRunOnServiceThread();
+ if (!isRoutingControlFeatureEnabled()) {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return true;
+ }
int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
// if the current device is a pure playback device
if (!mIsSwitchDevice
@@ -168,6 +178,10 @@
@ServiceThreadOnly
protected boolean handleRoutingInformation(HdmiCecMessage message) {
assertRunOnServiceThread();
+ if (!isRoutingControlFeatureEnabled()) {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return true;
+ }
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
// if the current device is a pure playback device
if (!mIsSwitchDevice
@@ -279,6 +293,12 @@
}
}
+ boolean isRoutingControlFeatureEnabled() {
+ synchronized (mLock) {
+ return mRoutingControlFeatureEnabled;
+ }
+ }
+
// Check if the device is trying to switch to the same input that is active right now.
// This can help avoid redundant port switching.
protected boolean isSwitchingToTheSameInput(@LocalActivePort int activePort) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index c005615..f8b3962 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
import android.annotation.Nullable;
+
import libcore.util.EmptyArray;
import java.util.Arrays;
@@ -111,12 +112,11 @@
@Override
public String toString() {
StringBuffer s = new StringBuffer();
- s.append(String.format("<%s> src: %d, dst: %d",
- opcodeToString(mOpcode), mSource, mDestination));
+ s.append(String.format("<%s> %X%X:%02X",
+ opcodeToString(mOpcode), mSource, mDestination, mOpcode));
if (mParams.length > 0) {
- s.append(", params:");
for (byte data : mParams) {
- s.append(String.format(" %02X", data));
+ s.append(String.format(":%02X", data));
}
}
return s.toString();
@@ -133,7 +133,7 @@
case Constants.MESSAGE_TUNER_STEP_DECREMENT:
return "Tuner Step Decrement";
case Constants.MESSAGE_TUNER_DEVICE_STATUS:
- return "Tuner Device Staus";
+ return "Tuner Device Status";
case Constants.MESSAGE_GIVE_TUNER_DEVICE_STATUS:
return "Give Tuner Device Status";
case Constants.MESSAGE_RECORD_ON:
@@ -207,7 +207,7 @@
case Constants.MESSAGE_DEVICE_VENDOR_ID:
return "Device Vendor Id";
case Constants.MESSAGE_VENDOR_COMMAND:
- return "Vendor Commandn";
+ return "Vendor Command";
case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_DOWN:
return "Vendor Remote Button Down";
case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_UP:
@@ -215,7 +215,7 @@
case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
return "Give Device Vendor Id";
case Constants.MESSAGE_MENU_REQUEST:
- return "Menu REquest";
+ return "Menu Request";
case Constants.MESSAGE_MENU_STATUS:
return "Menu Status";
case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
@@ -247,7 +247,7 @@
case Constants.MESSAGE_SET_EXTERNAL_TIMER:
return "Set External Timer";
case Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR:
- return "Repot Short Audio Descriptor";
+ return "Report Short Audio Descriptor";
case Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR:
return "Request Short Audio Descriptor";
case Constants.MESSAGE_INITIATE_ARC:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2d6e762..aabe1ad 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -26,6 +26,7 @@
import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
+import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -184,9 +185,10 @@
@Override
public void onReceive(Context context, Intent intent) {
assertRunOnServiceThread();
+ boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_OFF:
- if (isPowerOnOrTransient()) {
+ if (isPowerOnOrTransient() && !isReboot) {
onStandby(STANDBY_SCREEN_OFF);
}
break;
@@ -202,7 +204,7 @@
}
break;
case Intent.ACTION_SHUTDOWN:
- if (isPowerOnOrTransient()) {
+ if (isPowerOnOrTransient() && !isReboot) {
onStandby(STANDBY_SHUTDOWN);
}
break;
@@ -345,6 +347,10 @@
@Nullable
private Looper mIoLooper;
+ // Thread safe physical address
+ @GuardedBy("mLock")
+ private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
+
// Last input port before switching to the MHL port. Should switch back to this port
// when the mobile device sends the request one touch play with off.
// Gets invalidated if we go to other port/input.
@@ -564,7 +570,8 @@
Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Global.MHL_INPUT_SWITCHING_ENABLED,
- Global.MHL_POWER_CHARGE_ENABLED
+ Global.MHL_POWER_CHARGE_ENABLED,
+ Global.HDMI_CEC_SWITCH_ENABLED
};
for (String s : settings) {
resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
@@ -605,6 +612,14 @@
if (isTvDeviceEnabled()) {
tv().setSystemAudioControlFeatureEnabled(enabled);
}
+ if (isAudioSystemDevice()) {
+ audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
+ }
+ break;
+ case Global.HDMI_CEC_SWITCH_ENABLED:
+ if (isAudioSystemDevice()) {
+ audioSystem().setRoutingControlFeatureEnables(enabled);
+ }
break;
case Global.MHL_INPUT_SWITCHING_ENABLED:
setMhlInputChangeEnabled(enabled);
@@ -734,6 +749,10 @@
assertRunOnServiceThread();
HdmiPortInfo[] cecPortInfo = null;
+ synchronized (mLock) {
+ mPhysicalAddress = getPhysicalAddress();
+ }
+
// CEC HAL provides majority of the info while MHL does only MHL support flag for
// each port. Return empty array if CEC HAL didn't provide the info.
if (mCecController != null) {
@@ -1532,6 +1551,14 @@
}
@Override
+ public int getPhysicalAddress() {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ return mPhysicalAddress;
+ }
+ }
+
+ @Override
public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
enforceAccessPermission();
runOnServiceThread(new Runnable() {
@@ -1834,14 +1861,28 @@
Slog.w(TAG, "audio system is not in system audio mode");
return;
}
- int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+ audioSystem().reportAudioStatus(Constants.ADDR_TV);
+ }
+ });
+ }
- sendCecCommand(HdmiCecMessageBuilder
- .buildReportAudioStatus(
- device.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_TV,
- scaledVolume,
- isMute));
+ @Override
+ public void setSystemAudioModeOnForAudioOnlySource() {
+ enforceAccessPermission();
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isAudioSystemDevice()) {
+ Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
+ return;
+ }
+ if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
+ Slog.e(TAG, "System Audio Mode is not supported.");
+ return;
+ }
+ sendCecCommand(
+ HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
}
});
}
@@ -1851,27 +1892,28 @@
if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
pw.println("mProhibitMode: " + mProhibitMode);
- if (mCecController != null) {
- pw.println("mCecController: ");
- pw.increaseIndent();
- mCecController.dump(pw);
- pw.decreaseIndent();
- }
+ pw.println("mPowerStatus: " + mPowerStatus);
+
+ // System settings
+ pw.println("System_settings:");
+ pw.increaseIndent();
+ pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
+ pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
+ pw.decreaseIndent();
pw.println("mMhlController: ");
pw.increaseIndent();
mMhlController.dump(pw);
pw.decreaseIndent();
- pw.println("mPortInfo: ");
- pw.increaseIndent();
- for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
- pw.println("- " + hdmiPortInfo);
+ HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
+ if (mCecController != null) {
+ pw.println("mCecController: ");
+ pw.increaseIndent();
+ mCecController.dump(pw);
+ pw.decreaseIndent();
}
- pw.decreaseIndent();
- pw.println("mPowerStatus: " + mPowerStatus);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 2a8117f..2110682 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -20,9 +20,13 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.util.IndentingPrintWriter;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+
/**
* Various utilities to handle HDMI CEC messages.
@@ -317,4 +321,74 @@
info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(),
info.getVendorId(), info.getDisplayName(), newPowerStatus);
}
+
+ /**
+ * Dump a {@link SparseArray} to the print writer.
+ *
+ * <p>The dump is formatted:
+ * <pre>
+ * name:
+ * key = value
+ * key = value
+ * ...
+ * </pre>
+ */
+ static <T> void dumpSparseArray(IndentingPrintWriter pw, String name,
+ SparseArray<T> sparseArray) {
+ printWithTrailingColon(pw, name);
+ pw.increaseIndent();
+ int size = sparseArray.size();
+ for (int i = 0; i < size; i++) {
+ int key = sparseArray.keyAt(i);
+ T value = sparseArray.get(key);
+ pw.printPair(Integer.toString(key), value);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
+ private static void printWithTrailingColon(IndentingPrintWriter pw, String name) {
+ pw.println(name.endsWith(":") ? name : name.concat(":"));
+ }
+
+ /**
+ * Dump a {@link Map} to the print writer.
+ *
+ * <p>The dump is formatted:
+ * <pre>
+ * name:
+ * key = value
+ * key = value
+ * ...
+ * </pre>
+ */
+ static <K, V> void dumpMap(IndentingPrintWriter pw, String name, Map<K, V> map) {
+ printWithTrailingColon(pw, name);
+ pw.increaseIndent();
+ for (Map.Entry<K, V> entry: map.entrySet()) {
+ pw.printPair(entry.getKey().toString(), entry.getValue());
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
+ /**
+ * Dump a {@link Map} to the print writer.
+ *
+ * <p>The dump is formatted:
+ * <pre>
+ * name:
+ * value
+ * value
+ * ...
+ * </pre>
+ */
+ static <T> void dumpIterable(IndentingPrintWriter pw, String name, Iterable<T> values) {
+ printWithTrailingColon(pw, name);
+ pw.increaseIndent();
+ for (T value : values) {
+ pw.println(value);
+ }
+ pw.decreaseIndent();
+ }
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 269767a..dfb98c3 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -64,6 +64,7 @@
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.Log;
+import android.util.StatsLog;
import com.android.internal.app.IBatteryStats;
import com.android.internal.location.GpsNetInitiatedHandler;
@@ -1704,6 +1705,24 @@
", response: " + userResponse);
}
native_send_ni_response(notificationId, userResponse);
+
+ StatsLog.write(StatsLog.GNSS_NI_EVENT_REPORTED,
+ StatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_RESPONSE,
+ notificationId,
+ /* niType= */ 0,
+ /* needNotify= */ false,
+ /* needVerify= */ false,
+ /* privacyOverride= */ false,
+ /* timeout= */ 0,
+ /* defaultResponse= */ 0,
+ /* requestorId= */ null,
+ /* text= */ null,
+ /* requestorIdEncoding= */ 0,
+ /* textEncoding= */ 0,
+ mSuplEsEnabled,
+ mEnabled,
+ userResponse);
+
return true;
}
};
@@ -1753,6 +1772,22 @@
notification.textEncoding = textEncoding;
mNIHandler.handleNiNotification(notification);
+ StatsLog.write(StatsLog.GNSS_NI_EVENT_REPORTED,
+ StatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_REQUEST,
+ notification.notificationId,
+ notification.niType,
+ notification.needNotify,
+ notification.needVerify,
+ notification.privacyOverride,
+ notification.timeout,
+ notification.defaultResponse,
+ notification.requestorId,
+ notification.text,
+ notification.requestorIdEncoding,
+ notification.textEncoding,
+ mSuplEsEnabled,
+ mEnabled,
+ /* userResponse= */ 0);
}
/**
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 86bc9f3..fe91c63 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -52,7 +52,6 @@
mExtras = null;
setProperties(properties);
- setEnabled(true);
}
/** Sets the enabled state of this mock provider. */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 20eebe7..7323e93 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2311,22 +2311,22 @@
}
@Override
- public boolean areAppOverlaysAllowed(String pkg) {
- return areAppOverlaysAllowedForPackage(pkg, Binder.getCallingUid());
+ public boolean areBubblesAllowed(String pkg) {
+ return areBubblesAllowedForPackage(pkg, Binder.getCallingUid());
}
@Override
- public boolean areAppOverlaysAllowedForPackage(String pkg, int uid) {
- enforceSystemOrSystemUIOrSamePackage("Caller not system or systemui or same package",
- pkg);
- return mPreferencesHelper.areAppOverlaysAllowed(pkg, uid);
+ public boolean areBubblesAllowedForPackage(String pkg, int uid) {
+ enforceSystemOrSystemUIOrSamePackage(pkg,
+ "Caller not system or systemui or same package");
+ return mPreferencesHelper.areBubblessAllowed(pkg, uid);
}
@Override
- public void setAppOverlaysAllowed(String pkg, int uid, boolean allowed) {
+ public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
checkCallerIsSystem();
- mPreferencesHelper.setAppOverlaysAllowed(pkg, uid, allowed);
+ mPreferencesHelper.setBubblesAllowed(pkg, uid, allowed);
handleSavePolicyFile();
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 28f6972..7a21aa2 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -81,7 +81,7 @@
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_ID = "id";
- private static final String ATT_APP_OVERLAY = "overlay";
+ private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
private static final String ATT_PRIORITY = "priority";
private static final String ATT_VISIBILITY = "visibility";
private static final String ATT_IMPORTANCE = "importance";
@@ -94,8 +94,9 @@
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_SHOW_BADGE = true;
- private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
+ private static final boolean DEFAULT_ALLOW_BUBBLE = true;
private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
+
/**
* Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
* fields.
@@ -108,7 +109,7 @@
@IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
public @interface LockableAppFields {
int USER_LOCKED_IMPORTANCE = 0x00000001;
- int USER_LOCKED_APP_OVERLAY = 0x00000002;
+ int USER_LOCKED_BUBBLE = 0x00000002;
}
// pkg|uid => PackagePreferences
@@ -176,7 +177,7 @@
XmlUtils.readBooleanAttribute(
parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
XmlUtils.readBooleanAttribute(
- parser, ATT_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY));
+ parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
r.importance = XmlUtils.readIntAttribute(
parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
r.priority = XmlUtils.readIntAttribute(
@@ -272,11 +273,11 @@
private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
return getOrCreatePackagePreferences(pkg, uid,
DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
- DEFAULT_ALLOW_APP_OVERLAY);
+ DEFAULT_ALLOW_BUBBLE);
}
private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
- int priority, int visibility, boolean showBadge, boolean allowAppOverlay) {
+ int priority, int visibility, boolean showBadge, boolean allowBubble) {
final String key = packagePreferencesKey(pkg, uid);
synchronized (mPackagePreferences) {
PackagePreferences
@@ -290,7 +291,7 @@
r.priority = priority;
r.visibility = visibility;
r.showBadge = showBadge;
- r.appOverlay = allowAppOverlay;
+ r.allowBubble = allowBubble;
try {
createDefaultChannelIfNeeded(r);
@@ -392,7 +393,7 @@
|| r.channels.size() > 0
|| r.groups.size() > 0
|| r.delegate != null
- || r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY;
+ || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -405,8 +406,8 @@
if (r.visibility != DEFAULT_VISIBILITY) {
out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
- if (r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY) {
- out.attribute(null, ATT_APP_OVERLAY, Boolean.toString(r.appOverlay));
+ if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
+ out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
}
out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
@@ -452,14 +453,28 @@
out.endTag(null, TAG_RANKING);
}
- public void setAppOverlaysAllowed(String pkg, int uid, boolean allowed) {
+ /**
+ * Sets whether bubbles are allowed.
+ *
+ * @param pkg the package to allow or not allow bubbles for.
+ * @param uid the uid to allow or not allow bubbles for.
+ * @param allowed whether bubbles are allowed.
+ */
+ public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
- p.appOverlay = allowed;
- p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_APP_OVERLAY;
+ p.allowBubble = allowed;
+ p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
}
- public boolean areAppOverlaysAllowed(String pkg, int uid) {
- return getOrCreatePackagePreferences(pkg, uid).appOverlay;
+ /**
+ * Whether bubbles are allowed.
+ *
+ * @param pkg the package to check if bubbles are allowed for
+ * @param uid the uid to check if bubbles are allowed for.
+ * @return whether bubbles are allowed.
+ */
+ public boolean areBubblessAllowed(String pkg, int uid) {
+ return getOrCreatePackagePreferences(pkg, uid).allowBubble;
}
public int getAppLockedFields(String pkg, int uid) {
@@ -1232,8 +1247,8 @@
if (original.canShowBadge() != update.canShowBadge()) {
update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
}
- if (original.isAppOverlayAllowed() != update.isAppOverlayAllowed()) {
- update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_APP_OVERLAY);
+ if (original.isBubbleAllowed() != update.isBubbleAllowed()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
}
}
@@ -1654,7 +1669,7 @@
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
boolean showBadge = DEFAULT_SHOW_BADGE;
- boolean appOverlay = DEFAULT_ALLOW_APP_OVERLAY;
+ boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
List<String> futureOemLockedChannels = new ArrayList<>();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 945d7ad..6932390 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -239,6 +239,7 @@
import android.os.storage.StorageManagerInternal;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
+import android.permission.PermissionControllerManager;
import android.provider.MediaStore;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -317,7 +318,6 @@
import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionsState;
-import com.android.server.pm.permission.PermissionsState.PermissionState;
import com.android.server.security.VerityUtils;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -370,6 +370,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -443,6 +444,8 @@
private static final boolean ENABLE_FREE_CACHE_V2 =
SystemProperties.getBoolean("fw.free_cache_v2", true);
+ private static final long BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
+
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
private static final int NFC_UID = Process.NFC_UID;
@@ -19573,28 +19576,32 @@
throw new SecurityException("Only the system may call getPermissionGrantBackup()");
}
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- try {
- final XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_PERMISSION_BACKUP);
+ AtomicReference<byte[]> backup = new AtomicReference<>();
+ mContext.getSystemService(PermissionControllerManager.class).getRuntimePermissionBackup(
+ UserHandle.of(userId), mContext.getMainExecutor(), (b) -> {
+ synchronized (backup) {
+ backup.set(b);
+ backup.notifyAll();
+ }
+ });
- synchronized (mPackages) {
- serializeRuntimePermissionGrantsLPr(serializer, userId);
- }
+ long start = System.currentTimeMillis();
+ synchronized (backup) {
+ while (backup.get() == null) {
+ long timeLeft = start + BACKUP_TIMEOUT_MILLIS - System.currentTimeMillis();
+ if (timeLeft <= 0) {
+ return null;
+ }
- serializer.endTag(null, TAG_PERMISSION_BACKUP);
- serializer.endDocument();
- serializer.flush();
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Unable to write default apps for backup", e);
+ try {
+ backup.wait(timeLeft);
+ } catch (InterruptedException ignored) {
+ return null;
+ }
}
- return null;
}
- return dataStream.toByteArray();
+ return backup.get();
}
@Override
@@ -19620,66 +19627,6 @@
}
@GuardedBy("mPackages")
- private void serializeRuntimePermissionGrantsLPr(XmlSerializer serializer, final int userId)
- throws IOException {
- serializer.startTag(null, TAG_ALL_GRANTS);
-
- final int N = mSettings.mPackages.size();
- for (int i = 0; i < N; i++) {
- final PackageSetting ps = mSettings.mPackages.valueAt(i);
- boolean pkgGrantsKnown = false;
-
- PermissionsState packagePerms = ps.getPermissionsState();
-
- for (PermissionState state : packagePerms.getRuntimePermissionStates(userId)) {
- final int grantFlags = state.getFlags();
- // only look at grants that are not system/policy fixed
- if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) == 0) {
- final boolean isGranted = state.isGranted();
- // And only back up the user-twiddled state bits
- if (isGranted || (grantFlags & USER_RUNTIME_GRANT_MASK) != 0) {
- final String packageName = mSettings.mPackages.keyAt(i);
- if (!pkgGrantsKnown) {
- serializer.startTag(null, TAG_GRANT);
- serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
- pkgGrantsKnown = true;
- }
-
- final boolean userSet =
- (grantFlags & FLAG_PERMISSION_USER_SET) != 0;
- final boolean userFixed =
- (grantFlags & FLAG_PERMISSION_USER_FIXED) != 0;
- final boolean revoke =
- (grantFlags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
-
- serializer.startTag(null, TAG_PERMISSION);
- serializer.attribute(null, ATTR_PERMISSION_NAME, state.getName());
- if (isGranted) {
- serializer.attribute(null, ATTR_IS_GRANTED, "true");
- }
- if (userSet) {
- serializer.attribute(null, ATTR_USER_SET, "true");
- }
- if (userFixed) {
- serializer.attribute(null, ATTR_USER_FIXED, "true");
- }
- if (revoke) {
- serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
- }
- serializer.endTag(null, TAG_PERMISSION);
- }
- }
- }
-
- if (pkgGrantsKnown) {
- serializer.endTag(null, TAG_GRANT);
- }
- }
-
- serializer.endTag(null, TAG_ALL_GRANTS);
- }
-
- @GuardedBy("mPackages")
private void processRestoredPermissionGrantsLPr(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
String pkgName = null;
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 4884e91..4e71a05 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -2065,6 +2065,17 @@
mContext.unregisterReceiver(mShutdownEventReceiver);
cancelAnomalyAlarm();
cancelPullingAlarm();
+
+ BinderCallsStatsService.Internal binderStats =
+ LocalServices.getService(BinderCallsStatsService.Internal.class);
+ if (binderStats != null) {
+ binderStats.reset();
+ }
+
+ LooperStats looperStats = LocalServices.getService(LooperStats.class);
+ if (looperStats != null) {
+ looperStats.reset();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 5f56fe5..177f244 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -462,22 +462,19 @@
mOccluded = false;
mDismissingKeyguardActivity = null;
- // Only the top activity of the focused stack on each display may control it's
- // occluded state.
- final ActivityStack focusedStack = display.getFocusedStack();
- if (focusedStack != null) {
- final ActivityRecord topDismissing =
- focusedStack.getTopDismissingKeyguardActivity();
- mOccluded = focusedStack.topActivityOccludesKeyguard() || (topDismissing != null
- && focusedStack.topRunningActivityLocked() == topDismissing
- && controller.canShowWhileOccluded(
+ final ActivityStack stack = getStackForControllingOccluding(display);
+ if (stack != null) {
+ final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+ mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
+ && stack.topRunningActivityLocked() == topDismissing
+ && controller.canShowWhileOccluded(
true /* dismissKeyguard */,
false /* showWhenLocked */));
- if (focusedStack.getTopDismissingKeyguardActivity() != null) {
- mDismissingKeyguardActivity = focusedStack.getTopDismissingKeyguardActivity();
+ if (stack.getTopDismissingKeyguardActivity() != null) {
+ mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
}
- mOccluded |= controller.mWindowManager.isShowingDream();
}
+ mOccluded |= controller.mWindowManager.isShowingDream();
// TODO(b/113840485): Handle app transition for individual display, and apply occluded
// state change to secondary displays.
@@ -492,6 +489,23 @@
}
}
+ /**
+ * Gets the stack used to check the occluded state.
+ * <p>
+ * Only the top non-pinned activity of the focusable stack on each display can control its
+ * occlusion state.
+ */
+ private ActivityStack getStackForControllingOccluding(ActivityDisplay display) {
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ if (stack != null && stack.isFocusableAndVisible()
+ && !stack.inPinnedWindowingMode()) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
void dumpStatus(PrintWriter pw, String prefix) {
final StringBuilder sb = new StringBuilder();
sb.append(prefix);
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 4a553cf..e944858 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -1278,28 +1278,28 @@
}
/**
- * Checks if the root activity requires a particular orientation (either by override or
+ * Checks if the top activity requires a particular orientation (either by override or
* activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED.
*/
- private int getRootActivityRequestedOrientation() {
- ActivityRecord root = getRootActivity();
+ private int getTopActivityRequestedOrientation() {
+ ActivityRecord top = getTopActivity();
if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED
- || root == null) {
+ || top == null) {
return getRequestedOverrideConfiguration().orientation;
}
- int rootScreenOrientation = root.getOrientation();
- if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+ int screenOrientation = top.getOrientation();
+ if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
// NOSENSOR means the display's "natural" orientation, so return that.
ActivityDisplay display = mStack != null ? mStack.getDisplay() : null;
if (display != null && display.mDisplayContent != null) {
return mStack.getDisplay().mDisplayContent.getNaturalOrientation();
}
- } else if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+ } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// LOCKED means the activity's orientation remains unchanged, so return existing value.
- return root.getConfiguration().orientation;
- } else if (ActivityInfo.isFixedOrientationLandscape(rootScreenOrientation)) {
+ return top.getConfiguration().orientation;
+ } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) {
return ORIENTATION_LANDSCAPE;
- } else if (ActivityInfo.isFixedOrientationPortrait(rootScreenOrientation)) {
+ } else if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) {
return ORIENTATION_PORTRAIT;
}
return ORIENTATION_UNDEFINED;
@@ -2196,9 +2196,9 @@
// In FULLSCREEN mode, always start with empty bounds to indicate "fill parent"
outOverrideBounds.setEmpty();
- // If the task or its root activity require a different orientation, make it fit the
+ // If the task or its top activity requires a different orientation, make it fit the
// available bounds by scaling down its bounds.
- int forcedOrientation = getRootActivityRequestedOrientation();
+ int forcedOrientation = getTopActivityRequestedOrientation();
if (forcedOrientation != ORIENTATION_UNDEFINED
&& forcedOrientation != newParentConfig.orientation) {
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index b290bc5..bce944d 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -856,7 +856,7 @@
Return<void> GnssMeasurementCallback::gnssMeasurementCb_2_0(
const IGnssMeasurementCallback_V2_0::GnssData& data) {
- // TODO(b/119571122): implement gnssMeasurementCb_2_0
+ translateAndSetGnssData(data);
return Void();
}
@@ -894,9 +894,8 @@
return data.measurementCount;
}
-template<>
-size_t GnssMeasurementCallback::getMeasurementCount<IGnssMeasurementCallback_V1_1::GnssData>
- (const IGnssMeasurementCallback_V1_1::GnssData& data) {
+template<class T>
+size_t GnssMeasurementCallback::getMeasurementCount(const T& data) {
return data.measurements.size();
}
@@ -958,6 +957,17 @@
ADR_STATE_HALF_CYCLE_REPORTED));
}
+// Preallocate object as: JavaObject object(env, "android/location/GnssMeasurement");
+template<>
+void GnssMeasurementCallback::translateSingleGnssMeasurement
+ <IGnssMeasurementCallback_V2_0::GnssMeasurement>(
+ const IGnssMeasurementCallback_V2_0::GnssMeasurement* measurement_V2_0,
+ JavaObject& object) {
+ translateSingleGnssMeasurement(&(measurement_V2_0->v1_1), object);
+
+ SET(CodeType, (static_cast<int32_t>(measurement_V2_0->codeType)));
+}
+
jobject GnssMeasurementCallback::translateGnssClock(
JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssClock* clock) {
JavaObject object(env, "android/location/GnssClock");
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index c8e6782..4a48468 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -85,6 +85,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -793,6 +794,7 @@
}
@Test
+ @FlakyTest(bugId = 114098433)
public void testAllListeners() throws Exception {
final AppStateTrackerTestable instance = newInstance();
callStart(instance);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 41d5691..8171469 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -61,6 +61,7 @@
import android.view.Display;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -416,6 +417,7 @@
}
@Test
+ @FlakyTest(bugId = 119774928)
public void testEnabledState() throws Exception {
TestParoleListener paroleListener = new TestParoleListener();
mController.addListener(paroleListener);
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 83c1c76..94b21af 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3511,9 +3511,9 @@
}
@Test
- public void testAppOverlay() throws Exception {
- mBinderService.setAppOverlaysAllowed(PKG, mUid, false);
- assertFalse(mBinderService.areAppOverlaysAllowedForPackage(PKG, mUid));
+ public void testBubble() throws Exception {
+ mBinderService.setBubblesAllowed(PKG, mUid, false);
+ assertFalse(mBinderService.areBubblesAllowedForPackage(PKG, mUid));
}
@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 0fcfea7..24a1f8c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -809,7 +809,7 @@
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.setShowBadge(true);
- channel.setAllowAppOverlay(false);
+ channel.setAllowBubbles(false);
int lockMask = 0;
for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
@@ -826,7 +826,7 @@
assertFalse(savedChannel.canBypassDnd());
assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
- assertEquals(channel.canOverlayApps(), savedChannel.canOverlayApps());
+ assertEquals(channel.canBubble(), savedChannel.canBubble());
verify(mHandler, never()).requestSort();
}
@@ -840,7 +840,7 @@
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.setShowBadge(true);
- channel.setAllowAppOverlay(false);
+ channel.setAllowBubbles(false);
int lockMask = 0;
for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
@@ -857,7 +857,7 @@
assertFalse(savedChannel.canBypassDnd());
assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
- assertEquals(channel.canOverlayApps(), savedChannel.canOverlayApps());
+ assertEquals(channel.canBubble(), savedChannel.canBubble());
}
@Test
@@ -969,16 +969,16 @@
}
@Test
- public void testLockFields_appOverlay() {
+ public void testLockFields_allowBubble() {
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false);
assertEquals(0,
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel().getId(), false)
.getUserLockedFields());
final NotificationChannel update = getChannel();
- update.setAllowAppOverlay(false);
+ update.setAllowBubbles(false);
mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true);
- assertEquals(NotificationChannel.USER_LOCKED_ALLOW_APP_OVERLAY,
+ assertEquals(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE,
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update.getId(), false)
.getUserLockedFields());
}
@@ -2161,30 +2161,30 @@
}
@Test
- public void testAllowAppOverlay_defaults() throws Exception {
- assertTrue(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
+ public void testAllowBubbles_defaults() throws Exception {
+ assertTrue(mHelper.areBubblessAllowed(PKG_O, UID_O));
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
loadStreamXml(baos, false);
- assertTrue(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
+ assertTrue(mHelper.areBubblessAllowed(PKG_O, UID_O));
assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
}
@Test
- public void testAllowAppOverlay_xml() throws Exception {
- mHelper.setAppOverlaysAllowed(PKG_O, UID_O, false);
- assertFalse(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
- assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY,
+ public void testAllowBubbles_xml() throws Exception {
+ mHelper.setBubblesAllowed(PKG_O, UID_O, false);
+ assertFalse(mHelper.areBubblessAllowed(PKG_O, UID_O));
+ assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
mHelper.getAppLockedFields(PKG_O, UID_O));
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
loadStreamXml(baos, false);
- assertFalse(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
- assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY,
+ assertFalse(mHelper.areBubblessAllowed(PKG_O, UID_O));
+ assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
mHelper.getAppLockedFields(PKG_O, UID_O));
}
@@ -2290,14 +2290,14 @@
mHelper.lockChannelsForOEM(new String[] {PKG_O});
NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
- update.setAllowAppOverlay(false);
+ update.setAllowBubbles(false);
mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
assertEquals(IMPORTANCE_HIGH,
mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
assertEquals(false,
- mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canOverlayApps());
+ mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble());
mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index fa42289..51bebbb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,7 +39,9 @@
import androidx.test.filters.SmallTest;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
/**
@@ -52,15 +54,34 @@
@Presubmit
public class AppTransitionTests extends WindowTestsBase {
+ private static RootWindowContainer sOriginalRootWindowContainer;
+
private DisplayContent mDc;
+ @BeforeClass
+ public static void setUpRootWindowContainerMock() {
+ final WindowManagerService wm = WmServiceUtils.getWindowManagerService();
+ // For unit test, we don't need to test performSurfacePlacement to prevent some abnormal
+ // interaction with surfaceflinger native side.
+ sOriginalRootWindowContainer = wm.mRoot;
+ // Creating spied mock of RootWindowContainer shouldn't be done in @Before, since it will
+ // create unnecessary nested spied objects chain, because WindowManagerService object under
+ // test is a single instance shared among all tests that extend WindowTestsBase class.
+ // Instead it should be done once before running all tests in this test class.
+ wm.mRoot = spy(wm.mRoot);
+ doNothing().when(wm.mRoot).performSurfacePlacement(anyBoolean());
+ }
+
+ @AfterClass
+ public static void tearDownRootWindowContainerMock() {
+ final WindowManagerService wm = WmServiceUtils.getWindowManagerService();
+ wm.mRoot = sOriginalRootWindowContainer;
+ sOriginalRootWindowContainer = null;
+ }
+
@Before
public void setUp() throws Exception {
mDc = mWm.getDefaultDisplayContentLocked();
- // For unit test, we don't need to test performSurfacePlacement to prevent some
- // abnormal interaction with surfaceflinger native side.
- mWm.mRoot = spy(mWm.mRoot);
- doNothing().when(mWm.mRoot).performSurfacePlacement(anyBoolean());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 8a98cbe..cdb578d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -217,12 +217,17 @@
info.logicalHeight = fullScreenBounds.height();
ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
+ // Override display orientation. Normally this is available via DisplayContent, but DC
+ // is mocked-out.
+ display.getRequestedOverrideConfiguration().orientation =
+ Configuration.ORIENTATION_LANDSCAPE;
+ display.onRequestedOverrideConfigurationChanged(
+ display.getRequestedOverrideConfiguration());
ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
TaskRecord task = stack.getChildAt(0);
- ActivityRecord root = task.getRootActivity();
- ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
- assertEquals(root, task.getRootActivity());
+ ActivityRecord root = task.getTopActivity();
+ assertEquals(root, task.getTopActivity());
assertEquals(fullScreenBounds, task.getBounds());
@@ -233,16 +238,22 @@
assertTrue(task.getBounds().width() < task.getBounds().height());
assertEquals(fullScreenBounds.height(), task.getBounds().height());
- // Setting non-root app has no effect
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
- assertTrue(task.getBounds().width() < task.getBounds().height());
+ // Top activity gets used
+ ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
+ assertEquals(top, task.getTopActivity());
+ setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
+ assertTrue(task.getBounds().width() > task.getBounds().height());
+ assertEquals(task.getBounds().width(), fullScreenBounds.width());
// Setting app to unspecified restores
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_UNSPECIFIED);
+ setActivityRequestedOrientation(top, SCREEN_ORIENTATION_UNSPECIFIED);
assertEquals(fullScreenBounds, task.getBounds());
// Setting app to fixed landscape and changing display
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
+ setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
+ // simulate display orientation changing (normally done via DisplayContent)
+ display.getRequestedOverrideConfiguration().orientation =
+ Configuration.ORIENTATION_PORTRAIT;
display.setBounds(fullScreenBoundsPort);
assertTrue(task.getBounds().width() > task.getBounds().height());
assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index e95041b..09cdbd5 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -79,9 +79,9 @@
return visitor.can_compile();
}
-void CompileApkLayouts(const std::string& filename, CompilationTarget target,
- std::ostream& target_out) {
- auto assets = android::ApkAssets::Load(filename);
+namespace {
+void CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets>& assets,
+ CompilationTarget target, std::ostream& target_out) {
android::AssetManager2 resources;
resources.SetApkAssets({assets.get()});
@@ -155,5 +155,20 @@
target_out.write(image.ptr<const char>(), image.size());
}
}
+} // namespace
+
+void CompileApkLayouts(const std::string& filename, CompilationTarget target,
+ std::ostream& target_out) {
+ auto assets = android::ApkAssets::Load(filename);
+ CompileApkAssetsLayouts(assets, target, target_out);
+}
+
+void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
+ std::ostream& target_out) {
+ constexpr const char* friendly_name{"viewcompiler assets"};
+ auto assets = android::ApkAssets::LoadFromFd(
+ std::move(fd), friendly_name, /*system=*/false, /*force_shared_lib=*/false);
+ CompileApkAssetsLayouts(assets, target, target_out);
+}
} // namespace startop
diff --git a/startop/view_compiler/apk_layout_compiler.h b/startop/view_compiler/apk_layout_compiler.h
index c85ddd6..03bd545 100644
--- a/startop/view_compiler/apk_layout_compiler.h
+++ b/startop/view_compiler/apk_layout_compiler.h
@@ -19,12 +19,16 @@
#include <string>
+#include "android-base/unique_fd.h"
+
namespace startop {
enum class CompilationTarget { kJavaLanguage, kDex };
void CompileApkLayouts(const std::string& filename, CompilationTarget target,
std::ostream& target_out);
+void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
+ std::ostream& target_out);
} // namespace startop
diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc
index 871a421..11ecde2 100644
--- a/startop/view_compiler/main.cc
+++ b/startop/view_compiler/main.cc
@@ -49,6 +49,7 @@
DEFINE_bool(apk, false, "Compile layouts in an APK");
DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
+DEFINE_int32(infd, -1, "Read input from the given file descriptor");
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
DEFINE_string(package, "", "The package name for the generated class (required)");
@@ -95,7 +96,7 @@
int main(int argc, char** argv) {
constexpr size_t kProgramName = 0;
constexpr size_t kFileNameParam = 1;
- constexpr size_t kNumRequiredArgs = 2;
+ constexpr size_t kNumRequiredArgs = 1;
gflags::SetUsageMessage(
"Compile XML layout files into equivalent Java language code\n"
@@ -104,12 +105,11 @@
gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true);
gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package");
- if (argc != kNumRequiredArgs || cmd.is_default) {
+ if (argc < kNumRequiredArgs || cmd.is_default) {
gflags::ShowUsageWithFlags(argv[kProgramName]);
return 1;
}
- const char* const filename = argv[kFileNameParam];
const bool is_stdout = FLAGS_out == kStdoutFilename;
std::ofstream outfile;
@@ -118,13 +118,23 @@
}
if (FLAGS_apk) {
- startop::CompileApkLayouts(
- filename,
- FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage,
- is_stdout ? std::cout : outfile);
+ const startop::CompilationTarget target =
+ FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage;
+ if (FLAGS_infd >= 0) {
+ startop::CompileApkLayoutsFd(
+ android::base::unique_fd{FLAGS_infd}, target, is_stdout ? std::cout : outfile);
+ } else {
+ if (argc < 2) {
+ gflags::ShowUsageWithFlags(argv[kProgramName]);
+ return 1;
+ }
+ const char* const filename = argv[kFileNameParam];
+ startop::CompileApkLayouts(filename, target, is_stdout ? std::cout : outfile);
+ }
return 0;
}
+ const char* const filename = argv[kFileNameParam];
const string layout_name = startop::util::FindLayoutNameFromFilename(filename);
XMLDocument xml;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 2a648bd..8523554 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -501,4 +501,18 @@
*/
public static final String ACTION_LINE1_NUMBER_ERROR_DETECTED =
"com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED";
+
+ /**
+ * Broadcast action to notify radio bug.
+ *
+ * Requires the READ_PRIVILEGED_PHONE_STATE permission.
+ *
+ * @hide
+ */
+ public static final String ACTION_REPORT_RADIO_BUG =
+ "com.android.internal.telephony.ACTION_REPORT_RADIO_BUG";
+
+ // ACTION_REPORT_RADIO_BUG extra keys
+ public static final String EXTRA_SLOT_ID = "slotId";
+ public static final String EXTRA_RADIO_BUG_TYPE = "radioBugType";
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 28dd9b4..85871fe 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -1886,6 +1886,7 @@
if (creatorName != null) sbuf.append(" cname=" + creatorName);
if (lastUpdateUid != 0) sbuf.append(" luid=" + lastUpdateUid);
if (lastUpdateName != null) sbuf.append(" lname=" + lastUpdateName);
+ if (updateIdentifier != null) sbuf.append(" updateIdentifier=" + updateIdentifier);
sbuf.append(" lcuid=" + lastConnectUid);
sbuf.append(" userApproved=" + userApprovedAsString(userApproved));
sbuf.append(" noInternetAccessExpected=" + noInternetAccessExpected);
@@ -2281,6 +2282,7 @@
mRandomizedMacAddress = source.mRandomizedMacAddress;
macRandomizationSetting = source.macRandomizationSetting;
requirePMF = source.requirePMF;
+ updateIdentifier = source.updateIdentifier;
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index c744f18..7bff68a 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -59,6 +59,7 @@
WifiConfiguration config = new WifiConfiguration();
config.setPasspointManagementObjectTree(cookie);
config.trusted = false;
+ config.updateIdentifier = "1234";
MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
@@ -73,6 +74,7 @@
// lacking a useful config.equals, check two fields near the end.
assertEquals(cookie, reconfig.getMoTree());
assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
+ assertEquals(config.updateIdentifier, reconfig.updateIdentifier);
assertFalse(reconfig.trusted);
Parcel parcelWW = Parcel.obtain();
@@ -251,6 +253,18 @@
}
/**
+ * Verifies that updateIdentifier should be copied for copy constructor.
+ */
+ @Test
+ public void testUpdateIdentifierForCopyConstructor() {
+ WifiConfiguration config = new WifiConfiguration();
+ config.updateIdentifier = "1234";
+ WifiConfiguration copyConfig = new WifiConfiguration(config);
+
+ assertEquals(config.updateIdentifier, copyConfig.updateIdentifier);
+ }
+
+ /**
* Verifies that the serialization/de-serialization for softap config works.
*/
@Test